getQueue() {
91 | return queue;
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/test/java/com/notnoop/apns/internal/ApnsConnectionTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns.internal;
32 |
33 | import java.io.ByteArrayOutputStream;
34 | import javax.net.SocketFactory;
35 | import com.notnoop.apns.SimpleApnsNotification;
36 | import org.junit.Assert;
37 | import org.junit.Ignore;
38 | import org.junit.Test;
39 | import static com.notnoop.apns.internal.MockingUtils.*;
40 |
41 |
42 | @SuppressWarnings("deprecation")
43 | public class ApnsConnectionTest {
44 | private SimpleApnsNotification msg = new SimpleApnsNotification ("a87d8878d878a79", "{\"aps\":{}}");
45 |
46 | @Test
47 | public void simpleSocket() {
48 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
49 | SocketFactory factory = mockSocketFactory(baos, null);
50 | packetSentRegardless(factory, baos);
51 | }
52 |
53 | @Test
54 | @Ignore
55 | public void closedSocket() {
56 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
57 | SocketFactory factory = mockClosedThenOpenSocket(baos, null, true, 1);
58 | packetSentRegardless(factory, baos);
59 | }
60 |
61 | @Test
62 | public void errorOnce() {
63 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
64 | SocketFactory factory = mockClosedThenOpenSocket(baos, null, false, 1);
65 | packetSentRegardless(factory, baos);
66 | }
67 |
68 | @Test
69 | public void errorTwice() {
70 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
71 | SocketFactory factory = mockClosedThenOpenSocket(baos, null, false, 2);
72 | packetSentRegardless(factory, baos);
73 | }
74 |
75 | /**
76 | * Connection fails after three retries
77 | */
78 | @Test(expected = Exception.class)
79 | public void errorThrice() {
80 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
81 | SocketFactory factory = mockClosedThenOpenSocket(baos, null, false, 3);
82 | packetSentRegardless(factory, baos);
83 | }
84 |
85 | private void packetSentRegardless(SocketFactory sf, ByteArrayOutputStream baos) {
86 | ApnsConnectionImpl connection = new ApnsConnectionImpl(sf, "localhost", 80);
87 | connection.DELAY_IN_MS = 0;
88 | connection.sendMessage(msg);
89 | Assert.assertArrayEquals(msg.marshall(), baos.toByteArray());
90 | connection.close();
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/java/com/notnoop/apns/ApnsDelegate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns;
32 |
33 | /**
34 | * A delegate that gets notified of the status of notification delivery to the
35 | * Apple Server.
36 | *
37 | * The delegate doesn't get notified when the notification actually arrives at
38 | * the phone.
39 | */
40 | public interface ApnsDelegate {
41 |
42 | /**
43 | * Called when message was successfully sent to the Apple servers
44 | *
45 | * @param message the notification that was sent
46 | * @param resent whether the notification was resent after an error
47 | */
48 | public void messageSent(ApnsNotification message, boolean resent);
49 |
50 | /**
51 | * Called when the delivery of the message failed for any reason
52 | *
53 | * If message is null, then your notification has been rejected by Apple but
54 | * it has been removed from the cache so it is not possible to identify
55 | * which notification caused the error. In this case subsequent
56 | * notifications may be lost. If this happens you should consider increasing
57 | * your cacheLength value to prevent data loss.
58 | *
59 | * @param message the notification that was attempted to be sent
60 | * @param e the cause and description of the failure
61 | */
62 | public void messageSendFailed(ApnsNotification message, Throwable e);
63 |
64 | /**
65 | * The connection was closed and/or an error packet was received while
66 | * monitoring was turned on.
67 | *
68 | * @param e the delivery error
69 | * @param messageIdentifier id of the message that failed
70 | */
71 | public void connectionClosed(DeliveryError e, int messageIdentifier);
72 |
73 | /**
74 | * The resend cache needed a bigger size (while resending messages)
75 | *
76 | * @param newCacheLength new size of the resend cache.
77 | */
78 | public void cacheLengthExceeded(int newCacheLength);
79 |
80 | /**
81 | * A number of notifications has been queued for resending due to a error-response
82 | * packet being received.
83 | *
84 | * @param resendCount the number of messages being queued for resend
85 | */
86 | public void notificationsResent(int resendCount);
87 |
88 | /**
89 | * A no operation delegate that does nothing!
90 | */
91 | public final static ApnsDelegate EMPTY = new ApnsDelegateAdapter();
92 | }
93 |
--------------------------------------------------------------------------------
/src/test/java/com/notnoop/apns/internal/ApnsPooledConnectionTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns.internal;
32 |
33 | import com.notnoop.apns.ApnsNotification;
34 | import com.notnoop.exceptions.NetworkIOException;
35 | import org.junit.After;
36 | import org.junit.Before;
37 | import org.junit.Test;
38 |
39 | import java.util.concurrent.ExecutorService;
40 | import java.util.concurrent.Executors;
41 |
42 | import static org.mockito.Mockito.*;
43 |
44 | public class ApnsPooledConnectionTest {
45 |
46 | private ApnsConnection errorPrototype;
47 | private ApnsConnection prototype;
48 |
49 | private ExecutorService executorService;
50 |
51 | @Before
52 | public void setup() {
53 | errorPrototype = mock(ApnsConnection.class);
54 | when(errorPrototype.copy()).thenReturn(errorPrototype);
55 | doThrow(NetworkIOException.class).when(errorPrototype).sendMessage(any(ApnsNotification.class));
56 |
57 | prototype = mock(ApnsConnection.class);
58 | when(prototype.copy()).thenReturn(prototype);
59 | }
60 |
61 | @After
62 | public void cleanup() {
63 | if (executorService != null) {
64 | executorService.shutdownNow();
65 | }
66 | }
67 |
68 | @Test(expected = NetworkIOException.class)
69 | public void testSendMessage() throws Exception {
70 | ApnsPooledConnection conn = new ApnsPooledConnection(errorPrototype, 1, getSingleThreadExecutor());
71 | conn.sendMessage(mock(ApnsNotification.class));
72 | }
73 |
74 | @Test
75 | public void testCopyCalls() throws Exception {
76 | ApnsPooledConnection conn = new ApnsPooledConnection(prototype, 1, getSingleThreadExecutor());
77 | for (int i = 0; i < 10; i++) {
78 | conn.sendMessage(mock(ApnsNotification.class));
79 | }
80 | verify(prototype, times(1)).copy();
81 | }
82 |
83 | @Test
84 | public void testCloseCalls() throws Exception {
85 | ApnsPooledConnection conn = new ApnsPooledConnection(prototype, 1, getSingleThreadExecutor());
86 | conn.sendMessage(mock(ApnsNotification.class));
87 | conn.close();
88 | // should be closed twice because of the thread local copy
89 | verify(prototype, times(2)).close();
90 | }
91 |
92 | private ExecutorService getSingleThreadExecutor() {
93 | executorService = Executors.newSingleThreadExecutor();
94 | return executorService;
95 | }
96 | }
--------------------------------------------------------------------------------
/src/test/java/com/notnoop/apns/utils/Simulator/InputOutputSocket.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns.utils.Simulator;
32 |
33 | import java.io.DataOutputStream;
34 | import java.io.IOException;
35 | import java.net.Socket;
36 | import org.slf4j.Logger;
37 | import org.slf4j.LoggerFactory;
38 |
39 | /**
40 | * Wrap some of the boilerplate code using socket, enable passing around a socket together with its streams.
41 | */
42 | public class InputOutputSocket {
43 | private static final Logger LOGGER = LoggerFactory.getLogger(InputOutputSocket.class);
44 | private final Socket socket;
45 | private final ApnsInputStream inputStream;
46 | private final DataOutputStream outputStream;
47 |
48 | public InputOutputSocket(final Socket socket) throws IOException {
49 | if (socket == null) {
50 | throw new NullPointerException("socket may not be null");
51 | }
52 |
53 | this.socket = socket;
54 |
55 | // Hack, work around JVM deadlock ... https://community.oracle.com/message/10989561#10989561
56 | socket.setSoLinger(true, 1);
57 | outputStream = new DataOutputStream(socket.getOutputStream());
58 | inputStream = new ApnsInputStream(socket.getInputStream());
59 | }
60 |
61 | public Socket getSocket() {
62 | return socket;
63 | }
64 |
65 | public ApnsInputStream getInputStream() {
66 | return inputStream;
67 | }
68 |
69 | /*
70 | public DataOutputStream getOutputStream() {
71 | return outputStream;
72 | }
73 | */
74 |
75 |
76 |
77 | public synchronized void close() {
78 | try {
79 | inputStream.close();
80 | } catch (IOException e) {
81 | LOGGER.warn("Can not close inputStream properly", e);
82 | }
83 |
84 | try {
85 | outputStream.close();
86 | } catch (IOException e) {
87 | LOGGER.warn("Can not close outputStream properly", e);
88 | }
89 |
90 | try {
91 | socket.close();
92 | } catch (IOException e) {
93 | LOGGER.warn("Can not close socket properly", e);
94 | }
95 | }
96 |
97 | /**
98 | * Write data to the output stream while synchronized against close(). This hopefully fixes
99 | * sporadic test failures caused by a deadlock of write() and close()
100 | * @param bytes The data to write
101 | * @throws IOException if an error occurs
102 | */
103 | public void syncWrite(byte[] bytes) throws IOException {
104 | outputStream.write(bytes);
105 | outputStream.flush();
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/test/java/com/notnoop/apns/MainClass.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns;
32 |
33 | import java.io.FileInputStream;
34 | import java.io.FileNotFoundException;
35 | import java.util.Date;
36 | import java.util.Map;
37 | import java.util.Map.Entry;
38 | import com.notnoop.exceptions.InvalidSSLConfig;
39 |
40 | public class MainClass {
41 |
42 | /**
43 | * @param args Program arguments
44 | * @throws FileNotFoundException
45 | * @throws InvalidSSLConfig
46 | */
47 | public static void main(final String[] args) throws InvalidSSLConfig, FileNotFoundException {
48 | if (args.length != 4) {
49 | System.err.println("Usage: test \ntest p ./cert abc123 token");
50 | System.exit(777);
51 | }
52 |
53 | final ApnsDelegate delegate = new ApnsDelegate() {
54 | public void messageSent(final ApnsNotification message, final boolean resent) {
55 | System.out.println("Sent message " + message + " Resent: " + resent);
56 | }
57 |
58 | public void messageSendFailed(final ApnsNotification message, final Throwable e) {
59 | System.out.println("Failed message " + message);
60 |
61 | }
62 |
63 | public void connectionClosed(final DeliveryError e, final int messageIdentifier) {
64 | System.out.println("Closed connection: " + messageIdentifier + "\n deliveryError " + e.toString());
65 | }
66 |
67 | public void cacheLengthExceeded(final int newCacheLength) {
68 | System.out.println("cacheLengthExceeded " + newCacheLength);
69 |
70 | }
71 |
72 | public void notificationsResent(final int resendCount) {
73 | System.out.println("notificationResent " + resendCount);
74 | }
75 | };
76 |
77 | final ApnsService svc = APNS.newService()
78 | .withAppleDestination("p".equals(args[0]))
79 | .withCert(new FileInputStream(args[1]), args[2])
80 | .withDelegate(delegate)
81 | .build();
82 |
83 | final String goodToken = args[3];
84 |
85 | final String payload = APNS.newPayload().alertBody("Wrzlmbrmpf dummy alert").build();
86 |
87 | svc.start();
88 | System.out.println("Sending message");
89 | final ApnsNotification goodMsg = svc.push(goodToken, payload);
90 | System.out.println("Message id: " + goodMsg.getIdentifier());
91 |
92 | System.out.println("Getting inactive devices");
93 |
94 | final Map inactiveDevices = svc.getInactiveDevices();
95 |
96 | for (final Entry ent : inactiveDevices.entrySet()) {
97 | System.out.println("Inactive " + ent.getKey() + " at date " + ent.getValue());
98 | }
99 | System.out.println("Stopping service");
100 | svc.stop();
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/test/java/com/notnoop/apns/integration/ApnsDelegateRecorder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns.integration;
32 |
33 | import com.notnoop.apns.ApnsDelegate;
34 | import com.notnoop.apns.ApnsNotification;
35 | import com.notnoop.apns.DeliveryError;
36 |
37 | import java.util.ArrayList;
38 | import java.util.Collections;
39 | import java.util.List;
40 |
41 | import static org.junit.Assert.assertEquals;
42 |
43 | public class ApnsDelegateRecorder implements ApnsDelegate {
44 |
45 | private List sent = new ArrayList();
46 | private List failed = new ArrayList();
47 |
48 | @Override
49 | public void messageSent(ApnsNotification message, boolean resent) {
50 | sent.add(new MessageSentRecord(message, resent));
51 | }
52 |
53 | @Override
54 | public void messageSendFailed(ApnsNotification message, Throwable e) {
55 | failed.add(new MessageSentFailedRecord(message, e));
56 | }
57 |
58 | @Override
59 | public void connectionClosed(DeliveryError e, int messageIdentifier) {
60 | // not stubbed
61 | }
62 |
63 | @Override
64 | public void cacheLengthExceeded(int newCacheLength) {
65 | // not stubbed
66 | }
67 |
68 | @Override
69 | public void notificationsResent(int resendCount) {
70 | // not stubbed
71 | }
72 |
73 | public List getSent() {
74 | return Collections.unmodifiableList(sent);
75 | }
76 |
77 | public List getFailed() {
78 | return Collections.unmodifiableList(failed);
79 | }
80 |
81 | public static class MessageSentRecord {
82 | private final ApnsNotification notification;
83 | private final boolean resent;
84 |
85 | public MessageSentRecord(ApnsNotification notification, boolean resent) {
86 | this.notification = notification;
87 | this.resent = resent;
88 | }
89 |
90 | public ApnsNotification getNotification() {
91 | return notification;
92 | }
93 |
94 | public boolean isResent() {
95 | return resent;
96 | }
97 | }
98 |
99 | public static class MessageSentFailedRecord {
100 | private final ApnsNotification notification;
101 | private final Throwable ex;
102 |
103 | public MessageSentFailedRecord(ApnsNotification notification, Throwable ex) {
104 | this.notification = notification;
105 | this.ex = ex;
106 | }
107 |
108 | public ApnsNotification getNotification() {
109 | return notification;
110 | }
111 |
112 | @SuppressWarnings("unchecked")
113 | public T getException() {
114 | return (T) ex;
115 | }
116 |
117 | public void assertRecord(ApnsNotification notification, Throwable ex) {
118 | assertEquals(notification, getNotification());
119 | assertEquals(ex.getClass(), this.ex.getClass());
120 | }
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/src/test/java/com/notnoop/apns/internal/ApnsFeedbackConnectionTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns.internal;
32 |
33 | import java.io.ByteArrayInputStream;
34 | import java.io.InputStream;
35 |
36 | import javax.net.SocketFactory;
37 |
38 | import org.junit.Test;
39 |
40 | import static com.notnoop.apns.internal.ApnsFeedbackParsingUtils.*;
41 | import static com.notnoop.apns.internal.MockingUtils.mockClosedThenOpenSocket;
42 |
43 | public class ApnsFeedbackConnectionTest {
44 |
45 | InputStream simpleStream = new ByteArrayInputStream(simple);
46 | InputStream threeStream = new ByteArrayInputStream(three);
47 |
48 | /** Simple Parsing **/
49 | @Test
50 | public void rowParseOneDevice() {
51 | checkRawSimple(Utilities.parseFeedbackStreamRaw(simpleStream));
52 | }
53 |
54 | @Test
55 | public void threeParseTwoDevices() {
56 | checkRawThree(Utilities.parseFeedbackStreamRaw(threeStream));
57 | }
58 |
59 | @Test
60 | public void parsedSimple() {
61 | checkParsedSimple(Utilities.parseFeedbackStream(simpleStream));
62 | }
63 |
64 | @Test
65 | public void parsedThree() {
66 | checkParsedThree(Utilities.parseFeedbackStream(threeStream));
67 | }
68 |
69 | /** With Connection **/
70 | @Test
71 | public void connectionParsedOne() {
72 | SocketFactory sf = MockingUtils.mockSocketFactory(null, simpleStream);
73 | ApnsFeedbackConnection connection = new ApnsFeedbackConnection(sf, "localhost", 80);
74 | checkParsedSimple(connection.getInactiveDevices());
75 | }
76 |
77 | @Test
78 | public void connectionParsedThree() {
79 | SocketFactory sf = MockingUtils.mockSocketFactory(null, threeStream);
80 | ApnsFeedbackConnection connection = new ApnsFeedbackConnection(sf, "localhost", 80);
81 | checkParsedThree(connection.getInactiveDevices());
82 | }
83 |
84 | /** Check error recover **/
85 | @Test
86 | public void feedbackWithClosedSocket() {
87 | SocketFactory sf = mockClosedThenOpenSocket(null, simpleStream, true, 1);
88 | ApnsFeedbackConnection connection = new ApnsFeedbackConnection(sf, "localhost", 80);
89 | connection.DELAY_IN_MS = 0;
90 | checkParsedSimple(connection.getInactiveDevices());
91 | }
92 |
93 | @Test
94 | public void feedbackWithErrorOnce() {
95 | SocketFactory sf = mockClosedThenOpenSocket(null, simpleStream, true, 2);
96 | ApnsFeedbackConnection connection = new ApnsFeedbackConnection(sf, "localhost", 80);
97 | connection.DELAY_IN_MS = 0;
98 | checkParsedSimple(connection.getInactiveDevices());
99 | }
100 |
101 | /**
102 | * Connection fails after three retries
103 | */
104 | @Test(expected = Exception.class)
105 | public void feedbackWithErrorTwice() {
106 | SocketFactory sf = mockClosedThenOpenSocket(null, simpleStream, true, 3);
107 | ApnsFeedbackConnection connection = new ApnsFeedbackConnection(sf, "localhost", 80);
108 | connection.DELAY_IN_MS = 0;
109 | checkParsedSimple(connection.getInactiveDevices());
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/src/main/java/com/notnoop/apns/ReconnectPolicy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns;
32 |
33 | import com.notnoop.apns.internal.ReconnectPolicies;
34 |
35 | /**
36 | * Represents the reconnection policy for the library.
37 | *
38 | * Each object should be used exclusively for one
39 | * {@code ApnsService} only.
40 | */
41 | public interface ReconnectPolicy {
42 | /**
43 | * Returns {@code true} if the library should initiate a new
44 | * connection for sending the message.
45 | *
46 | * The library calls this method at every message push.
47 | *
48 | * @return true if the library should be reconnected
49 | */
50 | public boolean shouldReconnect();
51 |
52 | /**
53 | * Callback method to be called whenever the library
54 | * makes a new connection
55 | */
56 | public void reconnected();
57 |
58 | /**
59 | * Returns a deep copy of this reconnection policy, if needed.
60 | *
61 | * Subclasses may return this instance if the object is immutable.
62 | */
63 | public ReconnectPolicy copy();
64 |
65 | /**
66 | * Types of the library provided reconnection policies.
67 | *
68 | * This should capture most of the commonly used cases.
69 | */
70 | public enum Provided {
71 | /**
72 | * Only reconnect if absolutely needed, e.g. when the connection is dropped.
73 | *
74 | * Apple recommends using a persistent connection. This improves the latency of sending push notification messages.
75 | *
76 | * The down-side is that once the connection is closed ungracefully (e.g. because Apple server drops it), the library wouldn't
77 | * detect such failure and not warn against the messages sent after the drop before the detection.
78 | */
79 | NEVER {
80 | @Override
81 | public ReconnectPolicy newObject() {
82 | return new ReconnectPolicies.Never();
83 | }
84 | },
85 |
86 | /**
87 | * Makes a new connection if the current connection has lasted for more than half an hour.
88 | *
89 | * This is the recommended mode.
90 | *
91 | * This is the sweat-spot in my experiments between dropped connections while minimizing latency.
92 | */
93 | EVERY_HALF_HOUR {
94 | @Override
95 | public ReconnectPolicy newObject() {
96 | return new ReconnectPolicies.EveryHalfHour();
97 | }
98 | },
99 |
100 | /**
101 | * Makes a new connection for every message being sent.
102 | *
103 | * This option ensures that each message is actually
104 | * delivered to Apple.
105 | *
106 | * If you send a lot of messages though,
107 | * Apple may consider your requests to be a DoS attack.
108 | */
109 | EVERY_NOTIFICATION {
110 | @Override
111 | public ReconnectPolicy newObject() {
112 | return new ReconnectPolicies.Always();
113 | }
114 | };
115 |
116 | abstract ReconnectPolicy newObject();
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/test/java/com/notnoop/apns/ApnsGatewayServerSocket.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns;
32 |
33 | import java.io.BufferedOutputStream;
34 | import java.io.DataInputStream;
35 | import java.io.DataOutputStream;
36 | import java.io.IOException;
37 | import java.io.InputStream;
38 | import java.net.Socket;
39 | import java.util.concurrent.ExecutorService;
40 | import javax.net.ssl.SSLContext;
41 |
42 | /**
43 | * Represents the Apple APNS server. This allows testing outside of the Apple
44 | * servers.
45 | */
46 | @SuppressWarnings("deprecation")
47 | public class ApnsGatewayServerSocket extends AbstractApnsServerSocket {
48 | private final ApnsServerService apnsServerService;
49 |
50 | public ApnsGatewayServerSocket(SSLContext sslContext, int port,
51 | ExecutorService executorService,
52 | ApnsServerService apnsServerService,
53 | ApnsServerExceptionDelegate exceptionDelegate) throws IOException {
54 | super(sslContext, port, executorService, exceptionDelegate);
55 | this.apnsServerService = apnsServerService;
56 | }
57 |
58 | @Override
59 | void handleSocket(Socket socket) throws IOException {
60 | InputStream inputStream = socket.getInputStream();
61 | DataInputStream dataInputStream = new DataInputStream(inputStream);
62 | while (true) {
63 | int identifier = 0;
64 | try {
65 | int read = dataInputStream.read();
66 | if (read == -1) {
67 | break;
68 | }
69 |
70 | boolean enhancedFormat = read == 1;
71 | int expiry = 0;
72 | if (enhancedFormat) {
73 | identifier = dataInputStream.readInt();
74 | expiry = dataInputStream.readInt();
75 | }
76 |
77 | int deviceTokenLength = dataInputStream.readShort();
78 | byte[] deviceTokenBytes = toArray(inputStream,
79 | deviceTokenLength);
80 |
81 | int payloadLength = dataInputStream.readShort();
82 | byte[] payloadBytes = toArray(inputStream, payloadLength);
83 |
84 | ApnsNotification message;
85 | if (enhancedFormat) {
86 | message = new EnhancedApnsNotification(identifier, expiry,
87 | deviceTokenBytes, payloadBytes);
88 | } else {
89 | message = new SimpleApnsNotification(deviceTokenBytes,
90 | payloadBytes);
91 | }
92 | apnsServerService.messageReceived(message);
93 | } catch (IOException ioe) {
94 | writeResponse(socket, identifier, 8, 1);
95 | break;
96 | } catch (Exception e) {
97 | writeResponse(socket, identifier, 8, 1);
98 | break;
99 | }
100 | }
101 | }
102 |
103 | private void writeResponse(Socket socket, int identifier, int command,
104 | int status) {
105 | try {
106 | BufferedOutputStream bos = new BufferedOutputStream(
107 | socket.getOutputStream());
108 | DataOutputStream dataOutputStream = new DataOutputStream(bos);
109 | dataOutputStream.writeByte(command);
110 | dataOutputStream.writeByte(status);
111 | dataOutputStream.writeInt(identifier);
112 | dataOutputStream.flush();
113 | } catch (IOException ioe) {
114 | // if we can't write a response, nothing we can do
115 | }
116 | }
117 |
118 | private byte[] toArray(InputStream inputStream, int size)
119 | throws IOException {
120 | byte[] bytes = new byte[size];
121 | final DataInputStream dis = new DataInputStream(inputStream);
122 | dis.readFully(bytes);
123 | return bytes;
124 | }
125 | }
--------------------------------------------------------------------------------
/src/test/java/com/notnoop/apns/internal/SimpleApnsNotificationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns.internal;
32 |
33 | import static org.junit.Assert.*;
34 |
35 | import org.junit.experimental.theories.*;
36 | import org.junit.runner.RunWith;
37 |
38 | import com.notnoop.apns.PayloadBuilder;
39 | import com.notnoop.apns.SimpleApnsNotification;
40 | import com.notnoop.apns.internal.Utilities;
41 |
42 | import static com.notnoop.apns.PayloadBuilder.*;
43 | import static com.notnoop.apns.internal.Utilities.*;
44 |
45 | @SuppressWarnings("deprecation")
46 | @RunWith(Theories.class)
47 | public class SimpleApnsNotificationTest {
48 |
49 | // Device Tokens
50 | @DataPoints public static String[] deviceTokens =
51 | {
52 | "298893742908AB98C",
53 | "98234098203BACCCC93284092"
54 | };
55 |
56 | // Messages
57 | @DataPoints public static PayloadBuilder[] payloaders =
58 | {
59 | newPayload().alertBody("test").sound("default"),
60 | newPayload().sound("chimes").actionKey("Cancel"),
61 | newPayload().customField("notice", "this")
62 | };
63 |
64 | @Theory
65 | public void lengthConsistency(String deviceToken, PayloadBuilder payload) {
66 | SimpleApnsNotification msg = new SimpleApnsNotification(deviceToken, payload.build());
67 | assertEquals(msg.marshall().length, msg.length());
68 | }
69 |
70 | @Theory
71 | public void commandIsZero(String deviceToken, PayloadBuilder payload) {
72 | SimpleApnsNotification msg = new SimpleApnsNotification(deviceToken, payload.build());
73 | byte[] bytes = msg.marshall();
74 | assertEquals(0, /*command part*/ bytes[0]);
75 | }
76 |
77 | @Theory
78 | public void deviceTokenPart(String deviceToken, PayloadBuilder payload) {
79 | SimpleApnsNotification msg = new SimpleApnsNotification(deviceToken, payload.build());
80 | byte[] bytes = msg.marshall();
81 |
82 | byte[] dt = decodeHex(deviceToken);
83 | assertEquals(dt.length, /* found length */ ((bytes[1] & 0xff) << 8) + (bytes[2]& 0xff));
84 |
85 | // verify the device token part
86 | assertArrayEquals(dt, Utilities.copyOfRange(bytes, 3, 3 + dt.length));
87 | }
88 |
89 | @Theory
90 | public void payloadPart(String deviceToken, PayloadBuilder payload) {
91 | String payloadString = payload.build();
92 | SimpleApnsNotification msg = new SimpleApnsNotification(deviceToken, payloadString);
93 | byte[] bytes = msg.marshall();
94 |
95 | byte[] pl = toUTF8Bytes(payloadString);
96 |
97 | // in reverse
98 | int plBegin = bytes.length - pl.length;
99 |
100 | /// verify the payload part
101 | assertArrayEquals(pl, Utilities.copyOfRange(bytes, plBegin, bytes.length));
102 | assertEquals(pl.length, ((bytes[plBegin - 2] & 0xff) << 8) + (bytes[plBegin - 1] & 0xff));
103 | }
104 |
105 | @Theory
106 | public void allPartsLength(String deviceToken, PayloadBuilder payload) {
107 | String payloadString = payload.build();
108 | SimpleApnsNotification msg = new SimpleApnsNotification(deviceToken, payloadString);
109 | byte[] bytes = msg.marshall();
110 |
111 | int expectedLength = 1
112 | + 2 + decodeHex(deviceToken).length
113 | + 2 + toUTF8Bytes(payloadString).length;
114 | assertEquals(expectedLength, bytes.length);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/main/java/com/notnoop/apns/internal/ApnsPooledConnection.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns.internal;
32 |
33 | import java.util.concurrent.*;
34 | import com.notnoop.apns.ApnsNotification;
35 | import com.notnoop.exceptions.NetworkIOException;
36 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
37 | import org.slf4j.Logger;
38 | import org.slf4j.LoggerFactory;
39 |
40 | public class ApnsPooledConnection implements ApnsConnection {
41 | private static final Logger logger = LoggerFactory.getLogger(ApnsPooledConnection.class);
42 |
43 | private final ApnsConnection prototype;
44 | private final int max;
45 |
46 | private final ExecutorService executors;
47 | private final ConcurrentLinkedQueue prototypes;
48 |
49 | public ApnsPooledConnection(ApnsConnection prototype, int max) {
50 | this(prototype, max, Executors.newFixedThreadPool(max));
51 | }
52 |
53 | public ApnsPooledConnection(ApnsConnection prototype, int max, ExecutorService executors) {
54 | this.prototype = prototype;
55 | this.max = max;
56 |
57 | this.executors = executors;
58 | this.prototypes = new ConcurrentLinkedQueue();
59 | }
60 |
61 | private final ThreadLocal uniquePrototype =
62 | new ThreadLocal() {
63 | protected ApnsConnection initialValue() {
64 | ApnsConnection newCopy = prototype.copy();
65 | prototypes.add(newCopy);
66 | return newCopy;
67 | }
68 | };
69 |
70 | public void sendMessage(final ApnsNotification m) throws NetworkIOException {
71 | Future future = executors.submit(new Callable() {
72 | public Void call() throws Exception {
73 | uniquePrototype.get().sendMessage(m);
74 | return null;
75 | }
76 | });
77 | try {
78 | future.get();
79 | } catch (InterruptedException ie) {
80 | Thread.currentThread().interrupt();
81 | } catch (ExecutionException ee) {
82 | if (ee.getCause() instanceof NetworkIOException) {
83 | throw (NetworkIOException) ee.getCause();
84 | }
85 | }
86 | }
87 |
88 | public ApnsConnection copy() {
89 | // TODO: Should copy executor properly.... What should copy do
90 | // really?!
91 | return new ApnsPooledConnection(prototype, max);
92 | }
93 |
94 | public void close() {
95 | executors.shutdown();
96 | try {
97 | executors.awaitTermination(10, TimeUnit.SECONDS);
98 | } catch (InterruptedException e) {
99 | logger.warn("pool termination interrupted", e);
100 | }
101 | for (ApnsConnection conn : prototypes) {
102 | Utilities.close(conn);
103 | }
104 | Utilities.close(prototype);
105 | }
106 |
107 | public void testConnection() {
108 | prototype.testConnection();
109 | }
110 |
111 | public synchronized void setCacheLength(int cacheLength) {
112 | for (ApnsConnection conn : prototypes) {
113 | conn.setCacheLength(cacheLength);
114 | }
115 | }
116 |
117 | @SuppressFBWarnings(value = "UG_SYNC_SET_UNSYNC_GET", justification = "prototypes is a MT-safe container")
118 | public int getCacheLength() {
119 | return prototypes.peek().getCacheLength();
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/test/java/com/notnoop/apns/AbstractApnsServerSocket.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns;
32 |
33 | import java.io.IOException;
34 | import java.net.Socket;
35 | import java.util.concurrent.ExecutorService;
36 | import java.util.concurrent.TimeUnit;
37 |
38 | import javax.net.ssl.SSLContext;
39 | import javax.net.ssl.SSLServerSocket;
40 | import javax.net.ssl.SSLServerSocketFactory;
41 |
42 | /**
43 | * Represents the Apple server. This allows testing outside of the Apple
44 | * servers. Sub-classes should implement the specific handing of new socket
45 | * connections.
46 | */
47 | public abstract class AbstractApnsServerSocket {
48 | private final SSLServerSocket serverSocket;
49 | private final ExecutorService executorService;
50 | private final ApnsServerExceptionDelegate exceptionDelegate;
51 |
52 | public AbstractApnsServerSocket(SSLContext sslContext, int port,
53 | ExecutorService executorService,
54 | ApnsServerExceptionDelegate exceptionDelegate) throws IOException {
55 | SSLServerSocketFactory serverSocketFactory = sslContext
56 | .getServerSocketFactory();
57 | serverSocket = (SSLServerSocket) serverSocketFactory
58 | .createServerSocket(port);
59 | this.executorService = executorService;
60 | this.exceptionDelegate = exceptionDelegate;
61 | }
62 |
63 | /**
64 | * Start the server accept process. This method is non-blocking.
65 | */
66 | public final void start() {
67 | new Thread(new Runnable() {
68 | @Override
69 | public void run() {
70 | startAccept();
71 | }
72 | }).start();
73 | }
74 |
75 | @SuppressWarnings("InfiniteLoopStatement")
76 | private void startAccept() {
77 |
78 | try {
79 | while (true) {
80 | Socket accept = serverSocket.accept();
81 | // Work around JVM deadlock ... https://community.oracle.com/message/10989561#10989561
82 | accept.setSoLinger(true, 1);
83 | executorService.execute(new SocketHandler(accept));
84 | }
85 | } catch (IOException ioe) {
86 | executorService.shutdown();
87 | }
88 | }
89 |
90 | /**
91 | * Stops the server socket. This method is blocking.
92 | */
93 | public final void stop() {
94 | try {
95 | serverSocket.close();
96 | } catch (IOException ioe) {
97 | // don't care
98 | }
99 |
100 | executorService.shutdown(); // Disable new tasks from being submitted
101 | try {
102 | // Wait a while for existing tasks to terminate
103 | if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
104 | executorService.shutdownNow(); // Cancel currently executing
105 | // tasks
106 | // Wait a while for tasks to respond to being cancelled
107 | if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
108 | System.err.println("Pool did not terminate");
109 | }
110 | }
111 | } catch (InterruptedException ie) {
112 | // (Re-)Cancel if current thread also interrupted
113 | executorService.shutdownNow();
114 | // Preserve interrupt status
115 | Thread.currentThread().interrupt();
116 | }
117 | }
118 |
119 | private class SocketHandler implements Runnable {
120 | private final Socket socket;
121 |
122 | SocketHandler(Socket socket) {
123 | this.socket = socket;
124 | }
125 |
126 | @Override
127 | public void run() {
128 | try {
129 | handleSocket(socket);
130 | } catch (IOException ioe) {
131 | exceptionDelegate.handleRequestFailed(ioe);
132 | }
133 | }
134 | }
135 |
136 | abstract void handleSocket(Socket socket) throws IOException;
137 | }
138 |
--------------------------------------------------------------------------------
/src/main/java/com/notnoop/apns/internal/QueuedApnsService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns.internal;
32 |
33 | import java.util.Date;
34 | import java.util.Map;
35 | import java.util.concurrent.atomic.AtomicBoolean;
36 | import java.util.concurrent.BlockingQueue;
37 | import java.util.concurrent.Executors;
38 | import java.util.concurrent.LinkedBlockingQueue;
39 | import java.util.concurrent.ThreadFactory;
40 |
41 | import org.slf4j.Logger;
42 | import org.slf4j.LoggerFactory;
43 |
44 | import com.notnoop.apns.ApnsNotification;
45 | import com.notnoop.apns.ApnsService;
46 | import com.notnoop.exceptions.NetworkIOException;
47 |
48 | public class QueuedApnsService extends AbstractApnsService {
49 |
50 | private static final Logger logger = LoggerFactory.getLogger(QueuedApnsService.class);
51 |
52 | private ApnsService service;
53 | private BlockingQueue queue;
54 | private AtomicBoolean started = new AtomicBoolean(false);
55 |
56 | public QueuedApnsService(ApnsService service) {
57 | this(service, null);
58 | }
59 |
60 | public QueuedApnsService(ApnsService service, final ThreadFactory tf) {
61 | super(null);
62 | this.service = service;
63 | this.queue = new LinkedBlockingQueue();
64 | this.threadFactory = tf == null ? Executors.defaultThreadFactory() : tf;
65 | this.thread = null;
66 | }
67 |
68 | @Override
69 | public void push(ApnsNotification msg) {
70 | if (!started.get()) {
71 | throw new IllegalStateException("service hasn't be started or was closed");
72 | }
73 | queue.add(msg);
74 | }
75 |
76 | private final ThreadFactory threadFactory;
77 | private Thread thread;
78 | private volatile boolean shouldContinue;
79 |
80 | public void start() {
81 | if (started.getAndSet(true)) {
82 | // I prefer if we throw a runtime IllegalStateException here,
83 | // but I want to maintain semantic backward compatibility.
84 | // So it is returning immediately here
85 | return;
86 | }
87 |
88 | service.start();
89 | shouldContinue = true;
90 | thread = threadFactory.newThread(new Runnable() {
91 | public void run() {
92 | while (shouldContinue) {
93 | try {
94 | ApnsNotification msg = queue.take();
95 | service.push(msg);
96 | } catch (InterruptedException e) {
97 | // ignore
98 | } catch (NetworkIOException e) {
99 | // ignore: failed connect...
100 | } catch (Exception e) {
101 | // weird if we reached here - something wrong is happening, but we shouldn't stop the service anyway!
102 | logger.warn("Unexpected message caught... Shouldn't be here", e);
103 | }
104 | }
105 | }
106 | });
107 | thread.start();
108 | }
109 |
110 | public void stop() {
111 | started.set(false);
112 | shouldContinue = false;
113 | thread.interrupt();
114 | service.stop();
115 | }
116 |
117 | @Override
118 | public Map getInactiveDevices() throws NetworkIOException {
119 | return service.getInactiveDevices();
120 | }
121 |
122 | public void testConnection() throws NetworkIOException {
123 | service.testConnection();
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/src/test/java/com/notnoop/apns/internal/MockingUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns.internal;
32 |
33 | import java.io.IOException;
34 | import java.io.InputStream;
35 | import java.io.OutputStream;
36 | import java.net.Socket;
37 | import java.util.ArrayList;
38 | import java.util.List;
39 | import javax.net.SocketFactory;
40 | import org.mockito.invocation.InvocationOnMock;
41 | import org.mockito.stubbing.Answer;
42 | import org.mockito.stubbing.OngoingStubbing;
43 |
44 | import static org.mockito.Mockito.*;
45 |
46 | public class MockingUtils {
47 |
48 | static SocketFactory mockSocketFactory(OutputStream out, InputStream in) {
49 | try {
50 | Socket socket = mock(Socket.class);
51 | when(socket.getOutputStream()).thenReturn(out);
52 | when(socket.getInputStream()).thenReturn(in);
53 |
54 | SocketFactory factory = mock(SocketFactory.class);
55 | when(factory.createSocket()).thenReturn(socket);
56 | when(factory.createSocket(anyString(), anyInt())).thenReturn(socket);
57 |
58 | return factory;
59 | } catch (Exception e) {
60 | e.printStackTrace();
61 | throw new AssertionError("Cannot be here!");
62 | }
63 | }
64 |
65 | static SocketFactory mockClosedThenOpenSocket(OutputStream out, InputStream in, boolean isClosed, int failedTries) {
66 | try {
67 | List socketMocks = new ArrayList(failedTries + 1);
68 |
69 | for (int i = 0; i < failedTries; ++i) {
70 | Socket socket = mock(Socket.class);
71 | if (isClosed) {
72 | mockSocketClosed(socket);
73 | } else {
74 | when(socket.getOutputStream()).thenThrow(
75 | new IOException("simulated IOException"));
76 | doAnswer(new DynamicMockSocketClosed(socket)).when(socket).close();
77 | }
78 | socketMocks.add(socket);
79 | }
80 |
81 | Socket socket = mock(Socket.class);
82 | when(socket.getOutputStream()).thenReturn(out);
83 | when(socket.getInputStream()).thenReturn(in);
84 | when(socket.isConnected()).thenReturn(true);
85 | socketMocks.add(socket);
86 |
87 | SocketFactory factory = mock(SocketFactory.class);
88 | OngoingStubbing stubbing = when(factory.createSocket(anyString(), anyInt()));
89 | for (Socket t : socketMocks)
90 | stubbing = stubbing.thenReturn(t);
91 |
92 | return factory;
93 | } catch (Exception e) {
94 | e.printStackTrace();
95 | throw new AssertionError("Cannot be here!");
96 | }
97 | }
98 |
99 | private static void mockSocketClosed(final Socket socket) throws IOException {
100 | when(socket.isClosed()).thenReturn(true);
101 | when(socket.isConnected()).thenReturn(false);
102 | when(socket.getOutputStream()).thenThrow(
103 | new AssertionError("Should have checked for closed connection"));
104 | }
105 |
106 | /**
107 | * Change a mock socket's behaviour to be closed. Dynamically used from close()
108 | */
109 | private static class DynamicMockSocketClosed implements Answer {
110 | private final Socket socket;
111 |
112 | public DynamicMockSocketClosed(final Socket socket) {
113 | this.socket = socket;
114 | }
115 |
116 | @Override
117 | public Void answer(final InvocationOnMock invocation) throws Throwable {
118 | mockSocketClosed(socket);
119 | return null;
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/main/java/com/notnoop/apns/internal/ApnsFeedbackConnection.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns.internal;
32 |
33 | import java.io.IOException;
34 | import java.io.InputStream;
35 | import java.net.InetSocketAddress;
36 | import java.net.Proxy;
37 | import java.net.Socket;
38 | import java.util.Date;
39 | import java.util.Map;
40 | import javax.net.SocketFactory;
41 | import javax.net.ssl.SSLSocketFactory;
42 | import org.slf4j.Logger;
43 | import org.slf4j.LoggerFactory;
44 | import com.notnoop.exceptions.NetworkIOException;
45 |
46 | public class ApnsFeedbackConnection {
47 | private static final Logger logger = LoggerFactory.getLogger(ApnsFeedbackConnection.class);
48 |
49 | private final SocketFactory factory;
50 | private final String host;
51 | private final int port;
52 | private final Proxy proxy;
53 | private final int readTimeout;
54 | private final int connectTimeout;
55 | private final String proxyUsername;
56 | private final String proxyPassword;
57 |
58 | public ApnsFeedbackConnection(final SocketFactory factory, final String host, final int port) {
59 | this(factory, host, port, null, 0, 0, null, null);
60 | }
61 |
62 | public ApnsFeedbackConnection(final SocketFactory factory, final String host, final int port,
63 | final Proxy proxy, int readTimeout, int connectTimeout, final String proxyUsername, final String proxyPassword) {
64 | this.factory = factory;
65 | this.host = host;
66 | this.port = port;
67 | this.proxy = proxy;
68 | this.readTimeout = readTimeout;
69 | this.connectTimeout = connectTimeout;
70 | this.proxyUsername = proxyUsername;
71 | this.proxyPassword = proxyPassword;
72 | }
73 |
74 | int DELAY_IN_MS = 1000;
75 | private static final int RETRIES = 3;
76 |
77 | public Map getInactiveDevices() throws NetworkIOException {
78 | int attempts = 0;
79 | while (true) {
80 | try {
81 | attempts++;
82 | final Map result = getInactiveDevicesImpl();
83 |
84 | attempts = 0;
85 | return result;
86 | } catch (final Exception e) {
87 | logger.warn("Failed to retrieve invalid devices", e);
88 | if (attempts >= RETRIES) {
89 | logger.error("Couldn't get feedback connection", e);
90 | Utilities.wrapAndThrowAsRuntimeException(e);
91 | }
92 | Utilities.sleep(DELAY_IN_MS);
93 | }
94 | }
95 | }
96 |
97 | public Map getInactiveDevicesImpl() throws IOException {
98 | Socket proxySocket = null;
99 | Socket socket = null;
100 | try {
101 | if (proxy == null) {
102 | socket = factory.createSocket(host, port);
103 | } else if (proxy.type() == Proxy.Type.HTTP) {
104 | TlsTunnelBuilder tunnelBuilder = new TlsTunnelBuilder();
105 | socket = tunnelBuilder.build((SSLSocketFactory) factory, proxy, proxyUsername, proxyPassword, host, port);
106 | } else {
107 | proxySocket = new Socket(proxy);
108 | proxySocket.connect(new InetSocketAddress(host, port), connectTimeout);
109 | socket = ((SSLSocketFactory) factory).createSocket(proxySocket, host, port, false);
110 | }
111 | socket.setSoTimeout(readTimeout);
112 | socket.setKeepAlive(true);
113 | final InputStream stream = socket.getInputStream();
114 | return Utilities.parseFeedbackStream(stream);
115 | } finally {
116 | Utilities.close(socket);
117 | Utilities.close(proxySocket);
118 | }
119 | }
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/src/test/java/com/notnoop/apns/utils/FixedCertificates.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns.utils;
32 |
33 | import com.notnoop.apns.internal.SSLContextBuilder;
34 |
35 | import javax.net.ssl.SSLContext;
36 | import javax.net.ssl.X509TrustManager;
37 | import java.io.InputStream;
38 |
39 | public class FixedCertificates {
40 |
41 | public static final String CLIENT_STORE = "clientStore.p12";
42 | public static final String CLIENT_PASSWORD = "123456";
43 |
44 | public static final String CLIENT_MULTI_KEY_STORE = "clientStore.jks";
45 | public static final String CLIENT_MULTI_KEY_PASSWORD = "123456";
46 |
47 | public static final String SERVER_STORE = "serverStore.p12";
48 | public static final String SERVER_PASSWORD = "123456";
49 |
50 | public static final String SERVER_TRUST_STORE = "serverTrustStore.p12";
51 | public static final String SERVER_TRUST_PASSWORD = "123456";
52 |
53 | public static final String LOCALHOST = "localhost";
54 |
55 | public static SSLContext serverContext() {
56 | try {
57 | InputStream stream = FixedCertificates.class.getResourceAsStream("/" + SERVER_STORE);
58 | InputStream trustStream = FixedCertificates.class.getResourceAsStream("/" + SERVER_TRUST_STORE);
59 | assert stream != null;
60 | return new SSLContextBuilder()
61 | .withAlgorithm("sunx509")
62 | .withCertificateKeyStore(stream, SERVER_PASSWORD, "PKCS12")
63 | .withTrustKeyStore(trustStream, SERVER_TRUST_PASSWORD, "PKCS12")
64 | .build();
65 | } catch (Exception e) {
66 | throw new RuntimeException(e);
67 | }
68 | }
69 |
70 | public static SSLContext clientContext() {
71 | try {
72 | InputStream stream = FixedCertificates.class.getResourceAsStream("/" + CLIENT_STORE);
73 | assert stream != null;
74 | return new SSLContextBuilder()
75 | .withAlgorithm("sunx509")
76 | .withCertificateKeyStore(stream, CLIENT_PASSWORD, "PKCS12")
77 | .withTrustManager(new X509TrustManagerTrustAll())
78 | .build();
79 | } catch (Exception e) {
80 | throw new RuntimeException(e);
81 | }
82 | }
83 |
84 | public static SSLContext clientMultiKeyContext(String keyAlias) {
85 | try {
86 | InputStream stream = FixedCertificates.class.getResourceAsStream("/" + CLIENT_MULTI_KEY_STORE);
87 | assert stream != null;
88 | return new SSLContextBuilder()
89 | .withAlgorithm("sunx509")
90 | .withCertificateKeyStore(stream, CLIENT_MULTI_KEY_PASSWORD, "JKS", keyAlias)
91 | .withTrustManager(new X509TrustManagerTrustAll())
92 | .build();
93 | } catch (Exception e) {
94 | throw new RuntimeException(e);
95 | }
96 | }
97 |
98 | public static String clientCertPath() {
99 | return ClassLoader.getSystemResource(CLIENT_STORE).getPath();
100 | }
101 |
102 | static class X509TrustManagerTrustAll implements X509TrustManager {
103 | public boolean checkClientTrusted(java.security.cert.X509Certificate[] chain){
104 | return true;
105 | }
106 |
107 | public boolean isServerTrusted(java.security.cert.X509Certificate[] chain){
108 | return true;
109 | }
110 |
111 | public boolean isClientTrusted(java.security.cert.X509Certificate[] chain){
112 | return true;
113 | }
114 |
115 | public java.security.cert.X509Certificate[] getAcceptedIssuers() {
116 | return null;
117 | }
118 |
119 | public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {}
120 |
121 | public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {}
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/src/main/java/com/notnoop/apns/internal/BatchApnsService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns.internal;
32 |
33 | import static java.util.concurrent.Executors.defaultThreadFactory;
34 |
35 | import java.util.Queue;
36 | import java.util.concurrent.ConcurrentLinkedQueue;
37 | import java.util.concurrent.ScheduledExecutorService;
38 | import java.util.concurrent.ScheduledFuture;
39 | import java.util.concurrent.ScheduledThreadPoolExecutor;
40 | import java.util.concurrent.ThreadFactory;
41 | import java.util.concurrent.TimeUnit;
42 |
43 | import com.notnoop.apns.ApnsNotification;
44 | import com.notnoop.exceptions.NetworkIOException;
45 | import org.slf4j.Logger;
46 | import org.slf4j.LoggerFactory;
47 |
48 | public class BatchApnsService extends AbstractApnsService {
49 |
50 | private static final Logger logger = LoggerFactory.getLogger(BatchApnsService.class);
51 |
52 | /**
53 | * How many seconds to wait for more messages before batch is send.
54 | * Each message reset the wait time
55 | *
56 | * @see #maxBatchWaitTimeInSec
57 | */
58 | private int batchWaitTimeInSec = 5;
59 |
60 | /**
61 | * How many seconds can be batch delayed before execution.
62 | * This time is not exact amount after which the batch will run its roughly the time
63 | */
64 | private int maxBatchWaitTimeInSec = 10;
65 |
66 | private long firstMessageArrivedTime;
67 |
68 | private ApnsConnection prototype;
69 |
70 | private Queue batch = new ConcurrentLinkedQueue();
71 |
72 | private ScheduledExecutorService scheduleService;
73 | private ScheduledFuture> taskFuture;
74 |
75 | private Runnable batchRunner = new SendMessagesBatch();
76 |
77 | public BatchApnsService(ApnsConnection prototype, ApnsFeedbackConnection feedback, int batchWaitTimeInSec, int maxBachWaitTimeInSec, ThreadFactory tf) {
78 | this(prototype, feedback, batchWaitTimeInSec, maxBachWaitTimeInSec,
79 | new ScheduledThreadPoolExecutor(1,
80 | tf != null ? tf : defaultThreadFactory()));
81 | }
82 |
83 | public BatchApnsService(ApnsConnection prototype, ApnsFeedbackConnection feedback, int batchWaitTimeInSec, int maxBachWaitTimeInSec, ScheduledExecutorService executor) {
84 | super(feedback);
85 | this.prototype = prototype;
86 | this.batchWaitTimeInSec = batchWaitTimeInSec;
87 | this.maxBatchWaitTimeInSec = maxBachWaitTimeInSec;
88 | this.scheduleService = executor != null ? executor : new ScheduledThreadPoolExecutor(1, defaultThreadFactory());
89 | }
90 |
91 | public void start() {
92 | // no code
93 | }
94 |
95 | public void stop() {
96 | Utilities.close(prototype);
97 | if (taskFuture != null) {
98 | taskFuture.cancel(true);
99 | }
100 | scheduleService.shutdownNow();
101 | }
102 |
103 | public void testConnection() throws NetworkIOException {
104 | prototype.testConnection();
105 | }
106 |
107 | @Override
108 | public void push(ApnsNotification message) throws NetworkIOException {
109 | if (batch.isEmpty()) {
110 | firstMessageArrivedTime = System.nanoTime();
111 | }
112 |
113 | long sinceFirstMessageSec = (System.nanoTime() - firstMessageArrivedTime) / 1000 / 1000 / 1000;
114 |
115 | if (taskFuture != null && sinceFirstMessageSec < maxBatchWaitTimeInSec) {
116 | taskFuture.cancel(false);
117 | }
118 |
119 | batch.add(message);
120 |
121 | if (taskFuture == null || taskFuture.isDone()) {
122 | taskFuture = scheduleService.schedule(batchRunner, batchWaitTimeInSec, TimeUnit.SECONDS);
123 | }
124 | }
125 |
126 | class SendMessagesBatch implements Runnable {
127 | public void run() {
128 | ApnsConnection newConnection = prototype.copy();
129 | try {
130 | ApnsNotification msg;
131 | while ((msg = batch.poll()) != null) {
132 | try {
133 | newConnection.sendMessage(msg);
134 | } catch (NetworkIOException e) {
135 | logger.warn("Network exception sending message msg "+ msg.getIdentifier(), e);
136 | }
137 | }
138 | } finally {
139 | Utilities.close(newConnection);
140 | }
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/test/java/com/notnoop/apns/internal/QueuedApnsServiceTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns.internal;
32 |
33 | import static org.junit.Assert.*;
34 |
35 | import java.io.IOException;
36 | import java.util.concurrent.Semaphore;
37 |
38 | import org.junit.Test;
39 | import static org.mockito.Mockito.*;
40 |
41 | import com.notnoop.apns.ApnsNotification;
42 | import com.notnoop.apns.ApnsService;
43 | import com.notnoop.apns.EnhancedApnsNotification;
44 | import com.notnoop.exceptions.NetworkIOException;
45 |
46 | public class QueuedApnsServiceTest {
47 |
48 | @Test(expected = IllegalStateException.class)
49 | public void sendWithoutStarting() {
50 | QueuedApnsService service = new QueuedApnsService(null);
51 | service.push(notification);
52 | }
53 | EnhancedApnsNotification notification = new EnhancedApnsNotification(1,
54 | EnhancedApnsNotification.MAXIMUM_EXPIRY, "2342", "{}");
55 |
56 | @Test
57 | public void pushEventually() {
58 | ConnectionStub connection = spy(new ConnectionStub(0, 1));
59 | ApnsService service = newService(connection, null);
60 |
61 | service.push(notification);
62 | connection.semaphore.acquireUninterruptibly();
63 |
64 | verify(connection, times(1)).sendMessage(notification);
65 | }
66 |
67 | @Test
68 | public void pushEventuallySample() {
69 | ConnectionStub connection = spy(new ConnectionStub(0, 1));
70 | ApnsService service = newService(connection, null);
71 |
72 | service.push("2342", "{}");
73 | connection.semaphore.acquireUninterruptibly();
74 |
75 | verify(connection, times(1)).sendMessage(notification);
76 | }
77 |
78 | @Test
79 | public void doNotBlock() {
80 | final int delay = 10000;
81 | ConnectionStub connection = spy(new ConnectionStub(delay, 2));
82 | QueuedApnsService queued =
83 | new QueuedApnsService(new ApnsServiceImpl(connection, null));
84 | queued.start();
85 | long time1 = System.currentTimeMillis();
86 | queued.push(notification);
87 | queued.push(notification);
88 | long time2 = System.currentTimeMillis();
89 | assertTrue("queued.push() blocks", (time2 - time1) < delay);
90 |
91 | connection.interrupt();
92 | connection.semaphore.acquireUninterruptibly();
93 | verify(connection, times(2)).sendMessage(notification);
94 |
95 | queued.stop();
96 | }
97 |
98 | protected ApnsService newService(ApnsConnection connection, ApnsFeedbackConnection feedback) {
99 | ApnsService service = new ApnsServiceImpl(connection, null);
100 | ApnsService queued = new QueuedApnsService(service);
101 | queued.start();
102 | return queued;
103 | }
104 |
105 | static class ConnectionStub implements ApnsConnection {
106 |
107 | Semaphore semaphore;
108 | int delay;
109 |
110 | public ConnectionStub(int delay, int expectedCalls) {
111 | this.semaphore = new Semaphore(1 - expectedCalls);
112 | this.delay = delay;
113 | }
114 | volatile boolean stop;
115 |
116 | public synchronized void sendMessage(ApnsNotification m) {
117 | long time = System.currentTimeMillis();
118 | while (!stop && (System.currentTimeMillis() < time + delay)) {
119 | // WTF? Here was a busy wait for up to 10 seconds or until "stop" fired. Added: A tiny amount of sleep every round.
120 | try {
121 | Thread.sleep(2);
122 | } catch (InterruptedException e) {
123 | Thread.currentThread().interrupt();
124 | }
125 | }
126 | semaphore.release();
127 | }
128 |
129 | protected void interrupt() {
130 | stop = true;
131 | }
132 |
133 | public ApnsConnection copy() {
134 | throw new RuntimeException("Not implemented");
135 | }
136 |
137 | public void close() throws IOException {
138 | }
139 |
140 | public void testConnection() throws NetworkIOException {
141 | }
142 |
143 | public void setCacheLength(int cacheLength) {
144 | }
145 |
146 | public int getCacheLength() {
147 | return -1;
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/test/java/com/notnoop/apns/internal/BatchApnsServiceTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns.internal;
32 |
33 | import static org.mockito.Mockito.mock;
34 | import static org.mockito.Mockito.times;
35 | import static org.mockito.Mockito.verify;
36 | import static org.mockito.Mockito.when;
37 |
38 | import java.io.IOException;
39 | import java.util.concurrent.Executors;
40 |
41 | import org.junit.Before;
42 | import org.junit.Test;
43 |
44 | import com.notnoop.apns.ApnsNotification;
45 |
46 | public class BatchApnsServiceTest {
47 |
48 | private ApnsConnection prototype;
49 | private BatchApnsService service;
50 |
51 | private int delayTimeInSec = 2;
52 | private int delayTimeInSec_millis = delayTimeInSec * 1000; /* 2000 */
53 | private int delayTimeInSec1_2_millis = delayTimeInSec * 1000 / 2; /* 1000 */
54 | private int delayTimeInSec1_4_millis = delayTimeInSec * 1000 / 4; /* 500 */
55 | private int maxDelayTimeInSec = 2 * delayTimeInSec;
56 |
57 | @Before
58 | public void setup() {
59 | prototype = mock(ApnsConnection.class);
60 | when(prototype.copy()).thenReturn(prototype);
61 |
62 | service = new BatchApnsService(prototype, null, delayTimeInSec, maxDelayTimeInSec, Executors.defaultThreadFactory());
63 | }
64 |
65 | @Test
66 | public void simpleBatchWait_one() throws IOException, InterruptedException {
67 | // send message
68 | ApnsNotification message = service.push("1234", "{}");
69 |
70 | // make sure no message was send yet
71 | verify(prototype, times(0)).copy();
72 | verify(prototype, times(0)).sendMessage(message);
73 | verify(prototype, times(0)).close();
74 |
75 | Thread.sleep(delayTimeInSec_millis + /* for sure */250);
76 |
77 | // verify batch sends and close the connection
78 | verify(prototype, times(1)).copy();
79 | verify(prototype, times(1)).sendMessage(message);
80 | verify(prototype, times(1)).close();
81 | }
82 |
83 | @Test
84 | public void simpleBatchWait_multiple() throws IOException, InterruptedException {
85 | // send message
86 | ApnsNotification message1 = service.push("1234", "{}");
87 | Thread.sleep(delayTimeInSec1_2_millis);
88 | ApnsNotification message2 = service.push("4321", "{}");
89 |
90 | // make sure no message was send yet
91 | verify(prototype, times(0)).copy();
92 | verify(prototype, times(0)).sendMessage(message1);
93 | verify(prototype, times(0)).sendMessage(message2);
94 | verify(prototype, times(0)).close();
95 |
96 | Thread.sleep(delayTimeInSec1_4_millis * 3);
97 |
98 | // still no send
99 | verify(prototype, times(0)).copy();
100 | verify(prototype, times(0)).sendMessage(message1);
101 | verify(prototype, times(0)).sendMessage(message2);
102 | verify(prototype, times(0)).close();
103 |
104 | Thread.sleep(delayTimeInSec1_4_millis + /* for sure */250);
105 |
106 | // verify batch sends and close the connection
107 | verify(prototype, times(1)).copy();
108 | verify(prototype, times(1)).sendMessage(message1);
109 | verify(prototype, times(1)).sendMessage(message2);
110 | verify(prototype, times(1)).close();
111 | }
112 |
113 | @Test
114 | public void simpleBatchWait_maxDelay() throws IOException, InterruptedException {
115 | // send message
116 | ApnsNotification message1 = service.push("1234", "{}");
117 | Thread.sleep(delayTimeInSec1_4_millis * 3);
118 | ApnsNotification message2 = service.push("4321", "{}");
119 | Thread.sleep(delayTimeInSec1_4_millis * 3);
120 | ApnsNotification message3 = service.push("4321", "{}");
121 | Thread.sleep(delayTimeInSec1_4_millis * 3);
122 | ApnsNotification message4 = service.push("4321", "{}");
123 |
124 | // make sure no message was send yet
125 | verify(prototype, times(0)).copy();
126 | verify(prototype, times(0)).sendMessage(message1);
127 | verify(prototype, times(0)).sendMessage(message2);
128 | verify(prototype, times(0)).sendMessage(message3);
129 | verify(prototype, times(0)).sendMessage(message4);
130 | verify(prototype, times(0)).close();
131 |
132 | Thread.sleep(delayTimeInSec1_4_millis + /* for sure */250);
133 |
134 | // verify batch sends and close the connection
135 | verify(prototype, times(1)).copy();
136 | verify(prototype, times(1)).sendMessage(message1);
137 | verify(prototype, times(1)).sendMessage(message2);
138 | verify(prototype, times(1)).sendMessage(message3);
139 | verify(prototype, times(1)).sendMessage(message4);
140 | verify(prototype, times(1)).close();
141 | }
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/src/test/java/com/notnoop/apns/integration/ApnsSimulatorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns.integration;
32 |
33 | import com.notnoop.apns.DeliveryError;
34 | import org.junit.Assert;
35 | import org.junit.Ignore;
36 | import org.junit.Rule;
37 | import org.junit.Test;
38 | import org.junit.rules.Timeout;
39 | import org.mockito.Matchers;
40 | import uk.org.lidalia.slf4jext.Level;
41 | import uk.org.lidalia.slf4jtest.LoggingEvent;
42 | import uk.org.lidalia.slf4jtest.TestLoggerFactory;
43 |
44 | import java.util.List;
45 | import java.util.concurrent.TimeUnit;
46 |
47 | import static org.hamcrest.CoreMatchers.hasItem;
48 | import static org.hamcrest.CoreMatchers.not;
49 | import static org.junit.Assert.assertThat;
50 | import static org.mockito.Mockito.times;
51 | import static org.mockito.Mockito.verify;
52 |
53 | @SuppressWarnings("deprecation")
54 | public class ApnsSimulatorTest extends ApnsSimulatorTestBase {
55 |
56 | // final Logger logger = LoggerFactory.getLogger(ApnsSimulatorTest.class);
57 |
58 | //@Rule
59 | //public DumpThreadsOnErrorRule dump = new DumpThreadsOnErrorRule();
60 |
61 | @Rule
62 | public Timeout timeout = new Timeout(5000);
63 |
64 |
65 | @Test
66 | public void sendOne() throws InterruptedException {
67 | send(0);
68 | server.getQueue().poll(5, TimeUnit.SECONDS);
69 | assertIdle();
70 | assertDelegateSentCount(1);
71 | }
72 |
73 | @Test
74 | public void sendThree() throws InterruptedException {
75 | sendCount(3, 0);
76 | assertNumberReceived(3);
77 | assertDelegateSentCount(3);
78 | }
79 |
80 | @Test
81 | public void sendThousand() throws InterruptedException {
82 | TestLoggerFactory.getInstance().setPrintLevel(Level.INFO);
83 | sendCount(1000, 0);
84 | assertNumberReceived(1000);
85 | assertDelegateSentCount(1000);
86 | }
87 |
88 |
89 | @Test
90 | public void sendDelay() throws InterruptedException {
91 | send(-3);
92 | server.getQueue().poll(5, TimeUnit.SECONDS);
93 | assertIdle();
94 | assertDelegateSentCount(1);
95 | }
96 |
97 | @Test
98 | public void testConnectionClose() throws InterruptedException {
99 | send(8);
100 | assertNumberReceived(1);
101 | assertDelegateSentCount(1);
102 | verify(delegate, times(1)).connectionClosed(Matchers.any(DeliveryError.class), Matchers.anyInt());
103 | }
104 |
105 | @Test
106 | public void handleRetransmissionWithSeveralOutstandingMessages() throws InterruptedException {
107 | send(-1, -1, -1, -1, -1, 8, -1, -1, -1, -1, -1, -1, -1);
108 | assertNumberReceived(13);
109 | assertDelegateSentCount(13 + 7); // Initially sending all 13 notifications, then resend the last 7 ones
110 | verify(delegate, times(1)).connectionClosed(Matchers.any(DeliveryError.class), Matchers.anyInt());
111 | }
112 |
113 |
114 | @Test
115 | public void testClientDoesNotResendMessagesWhenServerClosesSocketWithoutErrorPacket() throws InterruptedException {
116 | send(-1, -1, -1, -1, -1, -100, -1, -1, -1, -1, -1, -1, -1);
117 | assertNumberReceived(6);
118 | }
119 |
120 | @Ignore
121 | @Test
122 | public void RaceCondition() {
123 | // TODO implement test & decide if fix is necessary afterwards.
124 | Assert.fail("Assumption: monitoring thread crashes in read() when the sender thread closes the connection first.");
125 | // Thus the last feedback message gets lost, thus we lose messages.
126 | }
127 |
128 | @Test
129 | public void abortNoWait() throws InterruptedException {
130 | send(8, 0);
131 | assertNumberReceived(2);
132 | }
133 |
134 | @Test
135 | public void doNotSpamLogWhenConnectionClosesBetweenFeedbackPackets() throws InterruptedException {
136 | // Don't spam a lot of information into the log when the socket closes at a "legal" location. (Just before
137 | // or after a feedback packet)
138 | send(-1, 8, -1);
139 | assertNumberReceived(3);
140 | final List allLoggingEvents = TestLoggerFactory.getAllLoggingEvents();
141 | assertThat(allLoggingEvents, not(hasItem(eventContains("Exception while waiting for error code"))));
142 | }
143 |
144 | @Test
145 | public void firstTokenBad_issue145() throws InterruptedException {
146 | // Test for Issue #145
147 | send(8, 0);
148 | assertNumberReceived(2);
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/test/java/com/notnoop/apns/integration/FeedbackTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns.integration;
32 |
33 | import java.io.IOException;
34 | import java.net.SocketTimeoutException;
35 | import javax.net.ssl.SSLContext;
36 | import com.notnoop.apns.APNS;
37 | import com.notnoop.apns.ApnsService;
38 | import com.notnoop.apns.utils.ApnsServerStub;
39 | import org.junit.After;
40 | import org.junit.Before;
41 | import org.junit.Test;
42 | import static com.notnoop.apns.internal.ApnsFeedbackParsingUtils.*;
43 | import static com.notnoop.apns.utils.FixedCertificates.*;
44 | import static org.junit.Assert.*;
45 |
46 | public class FeedbackTest {
47 |
48 | ApnsServerStub server;
49 | SSLContext clientContext = clientContext();
50 |
51 |
52 | @Before
53 | public void startup() {
54 | server = ApnsServerStub.prepareAndStartServer();
55 | }
56 |
57 | @After
58 | public void tearDown() {
59 | server.stop();
60 | server = null;
61 | }
62 |
63 | @Test
64 | public void simpleFeedback() throws IOException {
65 | server.getToSend().write(simple);
66 |
67 | ApnsService service =
68 | APNS.newService().withSSLContext(clientContext)
69 | .withGatewayDestination(LOCALHOST, server.getEffectiveGatewayPort())
70 | .withFeedbackDestination(LOCALHOST, server.getEffectiveFeedbackPort())
71 | .build();
72 |
73 | checkParsedSimple(service.getInactiveDevices());
74 | }
75 |
76 | @Test
77 | public void simpleFeedbackWithoutTimeout() throws IOException {
78 | server.getToSend().write(simple);
79 | server.getToWaitBeforeSend().set(2000);
80 | ApnsService service =
81 | APNS.newService().withSSLContext(clientContext)
82 | .withGatewayDestination(LOCALHOST, server.getEffectiveGatewayPort())
83 | .withFeedbackDestination(LOCALHOST, server.getEffectiveFeedbackPort())
84 | .withReadTimeout(3000)
85 | .build();
86 |
87 | checkParsedSimple(service.getInactiveDevices());
88 | }
89 |
90 | @Test()
91 | public void simpleFeedbackWithTimeout() throws IOException {
92 | server.getToSend().write(simple);
93 | server.getToWaitBeforeSend().set(5000);
94 | ApnsService service =
95 | APNS.newService().withSSLContext(clientContext)
96 | .withGatewayDestination(LOCALHOST, server.getEffectiveGatewayPort())
97 | .withFeedbackDestination(LOCALHOST, server.getEffectiveFeedbackPort())
98 | .withReadTimeout(1000)
99 | .build();
100 | try {
101 | service.getInactiveDevices();
102 | fail("RuntimeException expected");
103 | }
104 | catch(RuntimeException e) {
105 | assertEquals("Socket timeout exception expected",
106 | SocketTimeoutException.class, e.getCause().getClass() );
107 | }
108 | }
109 |
110 | @Test
111 | public void threeFeedback() throws IOException {
112 | server.getToSend().write(three);
113 |
114 | ApnsService service =
115 | APNS.newService().withSSLContext(clientContext)
116 | .withGatewayDestination(LOCALHOST, server.getEffectiveGatewayPort())
117 | .withFeedbackDestination(LOCALHOST, server.getEffectiveFeedbackPort())
118 | .build();
119 |
120 | checkParsedThree(service.getInactiveDevices());
121 | }
122 |
123 | @Test
124 | public void simpleQueuedFeedback() throws IOException {
125 | server.getToSend().write(simple);
126 |
127 | ApnsService service =
128 | APNS.newService().withSSLContext(clientContext)
129 | .withGatewayDestination(LOCALHOST, server.getEffectiveGatewayPort())
130 | .withFeedbackDestination(LOCALHOST, server.getEffectiveFeedbackPort())
131 | .asQueued()
132 | .build();
133 |
134 | checkParsedSimple(service.getInactiveDevices());
135 | }
136 |
137 | @Test
138 | public void threeQueuedFeedback() throws IOException {
139 | server.getToSend().write(three);
140 |
141 | ApnsService service =
142 | APNS.newService().withSSLContext(clientContext)
143 | .withGatewayDestination(LOCALHOST, server.getEffectiveGatewayPort())
144 | .withFeedbackDestination(LOCALHOST, server.getEffectiveFeedbackPort())
145 | .asQueued()
146 | .build();
147 |
148 | checkParsedThree(service.getInactiveDevices());
149 | }
150 |
151 | }
152 |
--------------------------------------------------------------------------------
/src/test/java/com/notnoop/apns/integration/ApnsConnectionResendTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2009, Mahmood Ali.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of Mahmood Ali. nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 | package com.notnoop.apns.integration;
32 |
33 | import com.notnoop.apns.APNS;
34 | import com.notnoop.apns.ApnsDelegate;
35 | import com.notnoop.apns.ApnsNotification;
36 | import com.notnoop.apns.ApnsService;
37 | import com.notnoop.apns.DeliveryError;
38 | import com.notnoop.apns.EnhancedApnsNotification;
39 | import com.notnoop.apns.integration.ApnsDelegateRecorder.MessageSentFailedRecord;
40 | import com.notnoop.apns.utils.FixedCertificates;
41 | import com.notnoop.apns.utils.Simulator.ApnsResponse;
42 | import com.notnoop.apns.utils.Simulator.ApnsSimulatorWithVerification;
43 | import com.notnoop.exceptions.ApnsDeliveryErrorException;
44 | import com.notnoop.exceptions.NetworkIOException;
45 | import org.junit.AfterClass;
46 | import org.junit.Before;
47 | import org.junit.Test;
48 |
49 | import java.util.List;
50 |
51 | import static com.notnoop.apns.utils.FixedCertificates.LOCALHOST;
52 | import static com.notnoop.apns.utils.FixedCertificates.clientContext;
53 | import static org.junit.Assert.assertEquals;
54 | import static org.junit.Assert.assertTrue;
55 |
56 | public class ApnsConnectionResendTest {
57 |
58 | private static EnhancedApnsNotification NOTIFICATION_0 = buildNotification(0);
59 | private static EnhancedApnsNotification NOTIFICATION_1 = buildNotification(1);
60 | private static EnhancedApnsNotification NOTIFICATION_2 = buildNotification(2);
61 | private static ApnsSimulatorWithVerification apnsSim;
62 |
63 | private ApnsDelegateRecorder delegateRecorder;
64 | private ApnsService testee;
65 |
66 | @Before
67 | public void setUp() {
68 | if (apnsSim == null) {
69 | apnsSim = new ApnsSimulatorWithVerification(FixedCertificates.serverContext().getServerSocketFactory());
70 | apnsSim.start();
71 | }
72 | apnsSim.reset();
73 | delegateRecorder = new ApnsDelegateRecorder();
74 | testee = build(delegateRecorder);
75 | }
76 |
77 | @AfterClass
78 | public static void tearDownClass() {
79 | if (apnsSim != null) {
80 | apnsSim.stop();
81 | apnsSim = null;
82 | }
83 | }
84 |
85 | /*
86 | * Test when we submit 3 messages to APNS 0, 1, 2. 0 is an error but we don't see the error response back until
87 | * 1,2 have already been submitted. Then at this point the network connection to APNS cannot be made, so that
88 | * when retrying the submissions we have to notify the client that delivery failed for 1 and 2.
89 | */
90 | @Test
91 | public void testGivenFailedSubmissionDueToErrorThenApnsDownWithNotificationsInBufferEnsureClientNotified()
92 | throws Exception {
93 |
94 | final DeliveryError deliveryError = DeliveryError.INVALID_PAYLOAD_SIZE;
95 |
96 | apnsSim.when(NOTIFICATION_0).thenDoNothing();
97 | apnsSim.when(NOTIFICATION_1).thenDoNothing();
98 | apnsSim.when(NOTIFICATION_2).thenRespond(ApnsResponse.returnErrorAndShutdown(deliveryError, NOTIFICATION_0));
99 |
100 | testee.push(NOTIFICATION_0);
101 | testee.push(NOTIFICATION_1);
102 | testee.push(NOTIFICATION_2);
103 |
104 | // Give some time for connection failure to take place
105 | Thread.sleep(5000);
106 | // Verify received expected notifications
107 | apnsSim.verify();
108 |
109 | // verify delegate calls
110 | assertEquals(3, delegateRecorder.getSent().size());
111 | final List failed = delegateRecorder.getFailed();
112 | assertEquals(3, failed.size());
113 | // first is failed delivery due to payload size
114 | failed.get(0).assertRecord(NOTIFICATION_0, new ApnsDeliveryErrorException(deliveryError));
115 | // second and third are due to not being able to connect to APNS
116 | assertNetworkIoExForRedelivery(NOTIFICATION_1, failed.get(1));
117 | assertNetworkIoExForRedelivery(NOTIFICATION_2, failed.get(2));
118 | }
119 |
120 | private void assertNetworkIoExForRedelivery(ApnsNotification notification, MessageSentFailedRecord failed) {
121 | failed.assertRecord(notification, new NetworkIOException());
122 | final NetworkIOException found = failed.getException();
123 | assertTrue(found.isResend());
124 | }
125 |
126 |
127 | private ApnsService build(ApnsDelegate delegate) {
128 | return APNS.newService()
129 | .withConnectTimeout(1000)
130 | .withSSLContext(clientContext())
131 | .withGatewayDestination(LOCALHOST, apnsSim.getEffectiveGatewayPort())
132 | .withFeedbackDestination(LOCALHOST, apnsSim.getEffectiveFeedbackPort())
133 | .withDelegate(delegate).build();
134 | }
135 |
136 | private static EnhancedApnsNotification buildNotification(int id) {
137 | final String deviceToken = ApnsSimulatorWithVerification.deviceTokenForId(id);
138 | return new EnhancedApnsNotification(id, 1, deviceToken, "{\"aps\":{}}");
139 | }
140 |
141 | }
142 |
--------------------------------------------------------------------------------