├── stresstest.sh ├── src ├── test │ ├── resources │ │ ├── clientStore.jks │ │ ├── clientStore.p12 │ │ ├── serverStore.p12 │ │ ├── serverTrustStore.p12 │ │ └── logback-test.xml │ └── java │ │ └── com │ │ └── notnoop │ │ └── apns │ │ ├── utils │ │ ├── Simulator │ │ │ ├── Action.java │ │ │ ├── ApnsNotificationWithAction.java │ │ │ ├── ApnsResponse.java │ │ │ ├── ApnsInputStream.java │ │ │ ├── FailingApnsServerSimulator.java │ │ │ └── InputOutputSocket.java │ │ ├── junit │ │ │ ├── Repeat.java │ │ │ ├── RepeatRule.java │ │ │ └── DumpThreadsOnErrorRule.java │ │ ├── StringTruncationTest.java │ │ └── FixedCertificates.java │ │ ├── ApnsServerExceptionDelegate.java │ │ ├── APNSTest.java │ │ ├── integration │ │ ├── ApnsSimulatorLongRunningTest.java │ │ ├── ApnsDelegateRecorder.java │ │ ├── ApnsSimulatorTest.java │ │ ├── FeedbackTest.java │ │ └── ApnsConnectionResendTest.java │ │ ├── ApnsSocketService.java │ │ ├── ApnsServerService.java │ │ ├── internal │ │ ├── ApnsServiceImplTest.java │ │ ├── SillyTests.java │ │ ├── TlsTunnelBuilderTest.java │ │ ├── UtilitiesTest.java │ │ ├── QueuedApnsServiceTCTest.java │ │ ├── ApnsConnectionTest.java │ │ ├── ApnsPooledConnectionTest.java │ │ ├── ApnsFeedbackConnectionTest.java │ │ ├── SimpleApnsNotificationTest.java │ │ ├── MockingUtils.java │ │ ├── QueuedApnsServiceTest.java │ │ └── BatchApnsServiceTest.java │ │ ├── ApnsFeedbackServerSocket.java │ │ ├── MainClass.java │ │ ├── ApnsGatewayServerSocket.java │ │ └── AbstractApnsServerSocket.java └── main │ └── java │ └── com │ └── notnoop │ ├── exceptions │ ├── ApnsException.java │ ├── RuntimeIOException.java │ ├── ApnsDeliveryErrorException.java │ ├── InvalidSSLConfig.java │ └── NetworkIOException.java │ └── apns │ ├── StartSendingApnsDelegate.java │ ├── ApnsDelegateAdapter.java │ ├── internal │ ├── ApnsConnection.java │ ├── ApnsServiceImpl.java │ ├── ReconnectPolicies.java │ ├── ApnsPooledConnection.java │ ├── QueuedApnsService.java │ ├── ApnsFeedbackConnection.java │ └── BatchApnsService.java │ ├── APNS.java │ ├── ApnsNotification.java │ ├── DeliveryError.java │ ├── ApnsDelegate.java │ └── ReconnectPolicy.java ├── .gitignore ├── .travis.yml ├── template.mf ├── docs └── cachedNotification_queue.md ├── Makefile.release ├── LICENSE.contributor ├── LICENSE ├── CHANGELOG └── tools └── generate_test_stores.sh /stresstest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | rm builds.logs 3 | while mvn clean install; do date >>builds.log; done 4 | -------------------------------------------------------------------------------- /src/test/resources/clientStore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notnoop/java-apns/HEAD/src/test/resources/clientStore.jks -------------------------------------------------------------------------------- /src/test/resources/clientStore.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notnoop/java-apns/HEAD/src/test/resources/clientStore.p12 -------------------------------------------------------------------------------- /src/test/resources/serverStore.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notnoop/java-apns/HEAD/src/test/resources/serverStore.p12 -------------------------------------------------------------------------------- /src/test/resources/serverTrustStore.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notnoop/java-apns/HEAD/src/test/resources/serverTrustStore.p12 -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .#* 3 | *.sw? 4 | bin 5 | target 6 | release.properties 7 | *~ 8 | .project 9 | .classpath 10 | .settings 11 | tags 12 | .idea 13 | *.iml 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: Java 2 | jdk: 3 | - oraclejdk8 4 | - openjdk7 5 | - oraclejdk7 6 | - openjdk6 7 | 8 | notifications: 9 | email: 10 | - java-apns@frohwalt.de 11 | -------------------------------------------------------------------------------- /template.mf: -------------------------------------------------------------------------------- 1 | Bundle-Name: com.notnoop.apns 2 | Bundle-ManifestVersion: 2 3 | Import-Template: 4 | org.slf4j.*;version="0", 5 | com.fasterxml.jackson.*;version="0", 6 | javax.net.*;version="0", 7 | edu.umd.cs.findbugs.annotations.*;version="0", 8 | org.apache.*;version="0" 9 | -------------------------------------------------------------------------------- /docs/cachedNotification_queue.md: -------------------------------------------------------------------------------- 1 | Reference: [APNS Problems](http://redth.codes/the-problem-with-apples-push-notification-ser/) 2 | 3 | java-apns maintains a per-connection sent queue (cachedNotifications) like described in the above article 4 | 5 | * java-apns queue is bounded (cacheNotification() will poll notifications out of the queue when the queue gets too big.) 6 | 7 | * It does not regularly check whether sent messages have been sent a few seconds ago to remove them from the sent queue. 8 | 9 | So if we send a lot of notifications without failure the old notifications will fall off the queue. This is typically ok (since they are "older" and probably have been sent successfully). The queue only serves to cache all the messages sent in between the client sending a bad notification and the APNS server replying. 10 | 11 | Improvements to consider: 12 | 13 | * Refactoring the queue into a full type, freeing the ApnsConnectionImpl from a lot of queue handling code. (no functional changes) 14 | 15 | * record the last send time in the notification (or a wrapper) and poll() messages out of the queue that are older than a certain threshold. (Would guarantee very short queues with low notification volume) 16 | 17 | * When queue gets full anyways (additionally to date handling) voluntarily inject a bad message to enforce an answer (+reconnect) from APNS (bad idea, SSL reconnect is expensive) 18 | -------------------------------------------------------------------------------- /Makefile.release: -------------------------------------------------------------------------------- 1 | .PHONY: all site site-prepare site-clone site-gen site-push 2 | 3 | all: prepare site repo 4 | 5 | CODE_GIT="git@github.com:notnoop/java-apns.git" 6 | BRANCH=gh-pages 7 | 8 | WORK_DIR=target/deploy 9 | SITE_DIR=${WORK_DIR}/site 10 | 11 | REPO_GIT="git@github.com:notnoop/m2-repo.git" 12 | REPO_DIR=${WORK_DIR}/repo 13 | 14 | prepare: 15 | rm -rf ${WORK_DIR} 16 | mkdir -p ${WORK_DIR} 17 | 18 | ################### Site ####################### 19 | 20 | site: site-prepare site-clone site-gen site-push 21 | 22 | site-prepare: 23 | rm -rf ${SITE_DIR} 24 | mkdir -p ${WORK_DIR} 25 | 26 | site-clone: 27 | git clone -b ${BRANCH} ${CODE_GIT} ${SITE_DIR} 28 | 29 | site-gen: 30 | rm -rf ${SITE_DIR}/* 31 | mvn site:site 32 | cp -r target/site/ ${SITE_DIR} 33 | 34 | site-push: 35 | cd ${SITE_DIR}; git add .; git commit -a -m "Updating site..." 36 | cd ${SITE_DIR}; git push -f 37 | 38 | 39 | ################### Repo ####################### 40 | 41 | repo: repo-gen repo-prepare repo-clone repo-copy repo-push 42 | 43 | repo-gen: 44 | mvn release:prepare 45 | mvn release:perform 46 | 47 | repo-prepare: 48 | rm -rf ${REPO_DIR} 49 | mkdir -p ${WORK_DIR} 50 | 51 | repo-clone: 52 | git clone -b ${BRANCH} ${REPO_GIT} ${REPO_DIR} 53 | 54 | repo-copy: 55 | cp -r target/checkout/${REPO_DIR}/ ${REPO_DIR} 56 | 57 | repo-push: 58 | cd ${REPO_DIR}; git add .; git commit -a -m "Updating release repo" 59 | cd ${REPO_DIR}; git push -f 60 | -------------------------------------------------------------------------------- /LICENSE.contributor: -------------------------------------------------------------------------------- 1 | All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Mahmood Ali. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2009, Mahmood Ali. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Mahmood Ali. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/utils/Simulator/Action.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 | public enum Action { 34 | DO_NOTHING, RETURN_ERROR, RETURN_ERROR_AND_SHUTDOWN 35 | } 36 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Changelog of java-apns 2 | 3 | Version 0.2.4.build-SNAPSHOT - (in progress) 4 | - Performance testing java-apns (#95) 5 | 6 | Version 0.2.3 - Mar 30, 2013 7 | - return back to com.notnoop.apns group id (#91) 8 | 9 | - Add no operation delegate adapter and ability to build service with keystore (#93) 10 | - Proxy support for Feedback connection (#92) 11 | - Make APNS class final 12 | - Batch APNS (#14) 13 | - Update dependencies (#85 #84) 14 | 15 | Version 0.2.2 - Jan 20, 2013 16 | - ApnsConnection resend unsent notifications after error 17 | - Do not stop QueuedApnsService on unsuccessful attempt to submit a message 18 | - Continuous integration builds on travis ci 19 | - Support Proxy.Type.HTTP. 20 | - Do not log error messages for retries when sending a message, rather 21 | log one when we can't send a message at all. 22 | - When there is an error (error packet or exception in parsing error packet) 23 | close the socket instead of waiting for APNS to close it. 24 | - java-apns now is an osgi bundle 25 | - Have ApnsService.push return sent messages 26 | - Add changelog 27 | 28 | Version 0.1.6 - Jan 13, 2011 29 | - Publish maven artifacts to central maven repo 30 | - Fix a bug cause error detection not to be disabled 31 | 32 | Version 0.1.5 - Aug 13, 2010 33 | - Add support for enhanced push notification 34 | - Enable error detection for dropped connections 35 | - Rely on slf4j as dependency instead of logback 36 | - Support launch images in notifications 37 | 38 | Version 0.1.4 - Apr 14, 2010 39 | - Support SOCKS proxies 40 | - Add better options for shrinking long alert messages (e.g. insert "...") 41 | - Fix a bug related to reconnection policy in pooled connections 42 | 43 | Version 0.1.3 - Feb 5, 2010 44 | - Add an ApnsService.testConnection for testing setups and connections 45 | - Add ApnsService delegate interface to monitor progress 46 | - Add better more descriptive Exceptions 47 | 48 | Version 0.1.2 and older 49 | Distance past... 50 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/utils/junit/Repeat.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.junit; 32 | 33 | import java.lang.annotation.Retention; 34 | import java.lang.annotation.RetentionPolicy; 35 | import java.lang.annotation.Target; 36 | 37 | @Retention(RetentionPolicy.RUNTIME) 38 | @Target({java.lang.annotation.ElementType.METHOD}) 39 | public @interface Repeat { 40 | public abstract int count(); 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/ApnsServerExceptionDelegate.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 failures. 35 | */ 36 | public interface ApnsServerExceptionDelegate { 37 | 38 | void handleRequestFailed(Throwable thr); 39 | 40 | public static final ApnsServerExceptionDelegate EMPTY = new ApnsServerExceptionDelegate() { 41 | @Override 42 | public void handleRequestFailed(Throwable thr) { 43 | } 44 | }; 45 | } -------------------------------------------------------------------------------- /src/main/java/com/notnoop/exceptions/ApnsException.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.exceptions; 32 | 33 | /** 34 | * Base class for all the exceptions thrown in Apns Library 35 | */ 36 | public abstract class ApnsException extends RuntimeException { 37 | private static final long serialVersionUID = -4756693306121825229L; 38 | 39 | public ApnsException() { super(); } 40 | public ApnsException(String message) { super(message); } 41 | public ApnsException(Throwable cause) { super(cause); } 42 | public ApnsException(String m, Throwable c) { super(m, c); } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/notnoop/apns/StartSendingApnsDelegate.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 also gets notified just before a notification is being delivered to the 35 | * Apple Server. 36 | */ 37 | public interface StartSendingApnsDelegate extends ApnsDelegate { 38 | 39 | /** 40 | * Called when message is about to be sent to the Apple servers. 41 | * 42 | * @param message the notification that is about to be sent 43 | * @param resent whether the notification is being resent after an error 44 | */ 45 | public void startSending(ApnsNotification message, boolean resent); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/notnoop/apns/ApnsDelegateAdapter.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 no operation delegate that does nothing! 35 | */ 36 | public class ApnsDelegateAdapter implements ApnsDelegate { 37 | 38 | public void messageSent(ApnsNotification message, boolean resent) { 39 | } 40 | 41 | public void messageSendFailed(ApnsNotification message, Throwable e) { 42 | } 43 | 44 | public void connectionClosed(DeliveryError e, int messageIdentifier) { 45 | } 46 | 47 | public void cacheLengthExceeded(int newCacheLength) { 48 | } 49 | 50 | public void notificationsResent(int resendCount) { 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/notnoop/apns/internal/ApnsConnection.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.Closeable; 34 | 35 | import com.notnoop.apns.ApnsNotification; 36 | import com.notnoop.exceptions.NetworkIOException; 37 | 38 | public interface ApnsConnection extends Closeable { 39 | 40 | //Default number of notifications to keep for error purposes 41 | public static final int DEFAULT_CACHE_LENGTH = 100; 42 | 43 | void sendMessage(ApnsNotification m) throws NetworkIOException; 44 | 45 | void testConnection() throws NetworkIOException; 46 | 47 | ApnsConnection copy(); 48 | 49 | void setCacheLength(int cacheLength); 50 | 51 | int getCacheLength(); 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/APNSTest.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 org.junit.Test; 34 | import static org.junit.Assert.*; 35 | import static org.hamcrest.CoreMatchers.*; 36 | 37 | /** 38 | * Silly Tests 39 | */ 40 | public class APNSTest { 41 | 42 | @Test 43 | public void testInstances() { 44 | assertThat(APNS.newPayload(), isA(PayloadBuilder.class)); 45 | assertThat(APNS.newService(), isA(ApnsServiceBuilder.class)); 46 | } 47 | 48 | @Test 49 | public void payloadShouldGetNewInstances() { 50 | assertNotSame(APNS.newPayload(), APNS.newPayload()); 51 | } 52 | 53 | @Test 54 | public void newServiceGetNewInstances() { 55 | assertNotSame(APNS.newService(), APNS.newService()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/integration/ApnsSimulatorLongRunningTest.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 org.junit.Test; 34 | import org.slf4j.Logger; 35 | import org.slf4j.LoggerFactory; 36 | 37 | @SuppressWarnings("deprecation") 38 | public class ApnsSimulatorLongRunningTest extends ApnsSimulatorTestBase { 39 | 40 | final Logger logger = LoggerFactory.getLogger(ApnsSimulatorLongRunningTest.class); 41 | 42 | @Test 43 | public void multipleTokensBad_issue145() throws InterruptedException { 44 | final int rounds = 15; 45 | for (int i = 0; i < rounds; ++i) { 46 | logger.debug("*********** "+i); 47 | send(8, 0); 48 | assertNumberReceived(2); 49 | } 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/notnoop/apns/APNS.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 | * The main class to interact with the APNS Service. 35 | * 36 | * Provides an interface to create the {@link ApnsServiceBuilder} and 37 | * {@code ApnsNotification} payload. 38 | * 39 | */ 40 | public final class APNS { 41 | 42 | private APNS() { throw new AssertionError("Uninstantiable class"); } 43 | 44 | /** 45 | * Returns a new Payload builder 46 | */ 47 | public static PayloadBuilder newPayload() { 48 | return new PayloadBuilder(); 49 | } 50 | 51 | /** 52 | * Returns a new APNS Service for sending iPhone notifications 53 | */ 54 | public static ApnsServiceBuilder newService() { 55 | return new ApnsServiceBuilder(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/ApnsSocketService.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 | 35 | public class ApnsSocketService { 36 | private final AbstractApnsServerSocket apnsPushServerSocket; 37 | private final AbstractApnsServerSocket apnsFeedbackServerSocket; 38 | 39 | public ApnsSocketService(AbstractApnsServerSocket apnsPushServerSocket, 40 | AbstractApnsServerSocket apnsFeedbackServerSocket) 41 | throws IOException { 42 | this.apnsPushServerSocket = apnsPushServerSocket; 43 | this.apnsFeedbackServerSocket = apnsFeedbackServerSocket; 44 | } 45 | 46 | public void start() { 47 | apnsPushServerSocket.start(); 48 | apnsFeedbackServerSocket.start(); 49 | } 50 | 51 | public void stop() { 52 | apnsPushServerSocket.stop(); 53 | apnsFeedbackServerSocket.stop(); 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/java/com/notnoop/exceptions/RuntimeIOException.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.exceptions; 32 | 33 | import java.io.IOException; 34 | 35 | /** 36 | * Signals that an I/O exception of some sort has occurred. This 37 | * class is the general class of exceptions produced by failed or 38 | * interrupted I/O operations. 39 | * 40 | * This is a RuntimeException, unlike the java.io.IOException 41 | */ 42 | public class RuntimeIOException extends ApnsException { 43 | private static final long serialVersionUID = 8665285084049041306L; 44 | 45 | public RuntimeIOException() { super(); } 46 | public RuntimeIOException(String message) { super(message); } 47 | public RuntimeIOException(IOException cause) { super(cause); } 48 | public RuntimeIOException(String m, IOException c) { super(m, c); } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/notnoop/exceptions/ApnsDeliveryErrorException.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 | /* 32 | * To change this template, choose Tools | Templates 33 | * and open the template in the editor. 34 | */ 35 | package com.notnoop.exceptions; 36 | 37 | import com.notnoop.apns.DeliveryError; 38 | 39 | /** 40 | * 41 | * @author kkirch 42 | */ 43 | public class ApnsDeliveryErrorException extends ApnsException { 44 | 45 | private final DeliveryError deliveryError; 46 | 47 | public ApnsDeliveryErrorException(DeliveryError error) { 48 | this.deliveryError = error; 49 | } 50 | 51 | @Override 52 | public String getMessage() { 53 | return "Failed to deliver notification with error code " + deliveryError.code(); 54 | } 55 | 56 | public DeliveryError getDeliveryError() { 57 | return deliveryError; 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/notnoop/apns/internal/ApnsServiceImpl.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 | 36 | public class ApnsServiceImpl extends AbstractApnsService { 37 | private ApnsConnection connection; 38 | 39 | public ApnsServiceImpl(ApnsConnection connection, ApnsFeedbackConnection feedback) { 40 | super(feedback); 41 | this.connection = connection; 42 | } 43 | 44 | @Override 45 | public void push(ApnsNotification msg) throws NetworkIOException { 46 | connection.sendMessage(msg); 47 | } 48 | 49 | public void start() { 50 | } 51 | 52 | public void stop() { 53 | Utilities.close(connection); 54 | } 55 | 56 | public void testConnection() { 57 | connection.testConnection(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/ApnsServerService.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.util.Collections; 34 | import java.util.Date; 35 | import java.util.Map; 36 | 37 | /** 38 | * A delegate that gets notified of the delivery of messages. 39 | */ 40 | public interface ApnsServerService { 41 | 42 | 43 | /** 44 | * Called when message was successfully received 45 | * 46 | * @param message the notification that was received. 47 | * @throws Exception 48 | */ 49 | void messageReceived(ApnsNotification message) throws Exception; 50 | 51 | 52 | /** 53 | * Called to determine if any of the devices is judged to be inactive. 54 | * 55 | * @return a map of inactive devices. 56 | */ 57 | Map getInactiveDevices(); 58 | 59 | public static final ApnsServerService EMPTY = new ApnsServerService() { 60 | @Override 61 | public void messageReceived(ApnsNotification message) throws Exception { 62 | } 63 | 64 | @Override 65 | public Map getInactiveDevices() { 66 | return Collections.emptyMap(); 67 | } 68 | }; 69 | } -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/utils/junit/RepeatRule.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.junit; 32 | 33 | 34 | import org.junit.rules.TestRule; 35 | import org.junit.runner.Description; 36 | import org.junit.runners.model.Statement; 37 | 38 | public class RepeatRule implements TestRule { 39 | 40 | @Override 41 | public Statement apply(Statement base, Description description) { 42 | Repeat repeat = description.getAnnotation(Repeat.class); 43 | if (repeat != null) { 44 | return new RepeatStatement(repeat.count(), base); 45 | } 46 | return base; 47 | } 48 | 49 | private static class RepeatStatement extends Statement { 50 | 51 | private final int count; 52 | private final Statement base; 53 | 54 | private RepeatStatement(int count, Statement base) { 55 | this.count = count; 56 | this.base = base; 57 | } 58 | 59 | @Override 60 | public void evaluate() throws Throwable { 61 | for (int i = count; i > 0; i--) { 62 | base.evaluate(); 63 | } 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /tools/generate_test_stores.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | KEYSIZE=1024 4 | VALIDITY=1460 5 | rm -f serverStore.p12 serverTrustStore.p12 caKey.pem caCert.pem request.crs clientStore.p12 clientCert.crt clientStore.jks 6 | 7 | echo ---=== Generating Certificates ===--- 8 | echo This tools will request several parameters. 9 | echo The names are free, but it is recommended 10 | echo to enter a name that identifies the certificate 11 | echo for example TestServer, TestCA, TestClient, TestInvalidClient 12 | echo Enter 123456 when asked for a password 13 | echo 14 | 15 | # Server Cert 16 | echo --== Generating Server Certificate ==-- 17 | keytool -genkey -keyalg RSA -alias notnoop-server \ 18 | -keystore serverStore.p12 -storepass 123456 -storetype PKCS12 \ 19 | -validity ${VALIDITY} -keysize ${KEYSIZE} 20 | keytool -exportcert -alias notnoop-server \ 21 | -keystore serverStore.p12 -storepass 123456 -storetype PKCS12 | keytool -printcert 22 | 23 | # Client Cert CA 24 | echo --== Generating Client CA Certificate ==-- 25 | keytool -genkey -keyalg RSA -alias notnoop-ca \ 26 | -keystore serverTrustStore.p12 -storetype pkcs12 -storepass 123456 \ 27 | -validity ${VALIDITY} -keysize ${KEYSIZE} 28 | keytool -exportcert -alias notnoop-ca \ 29 | -keystore serverTrustStore.p12 -storepass 123456 -storetype PKCS12 | keytool -printcert 30 | 31 | openssl pkcs12 -in serverTrustStore.p12 -nocerts -out caKey.pem 32 | openssl pkcs12 -in serverTrustStore.p12 -clcerts -nokeys -out caCert.pem 33 | 34 | echo --== Generating Client Certificate ==-- 35 | keytool -genkey -keyalg RSA -keystore clientStore.p12 -storepass 123456 -storetype PKCS12 -alias notnoop-client \ 36 | -validity ${VALIDITY} -keysize ${KEYSIZE} 37 | keytool -certreq -keystore clientStore.p12 -storepass 123456 -storetype PKCS12 -alias notnoop-client -file request.crs 38 | 39 | echo --== Signing Client Certificate with CA ==-- 40 | openssl x509 -req -CA CACert.pem -CAkey CAKey.pem -in request.crs -out clientCert.crt \ 41 | -days ${VALIDITY} -CAcreateserial -outform PEM -set_serial 1 42 | cat caCert.pem >> clientCert.crt 43 | keytool -import -keystore clientStore.p12 -storepass 123456 -storetype PKCS12 -file clientCert.crt -alias notnoop-client 44 | keytool -exportcert -alias notnoop-client \ 45 | -keystore clientStore.p12 -storepass 123456 -storetype PKCS12 | keytool -printcert 46 | 47 | echo --== Generating JKS Client Keystore with Client Certificate ==-- 48 | keytool -importkeystore \ 49 | -srckeystore clientStore.p12 -srcstorepass 123456 -srcstoretype PKCS12 -srcalias notnoop-client \ 50 | -destkeystore clientStore.jks -deststorepass 123456 -deststoretype JKS -destalias notnoop-client 51 | 52 | echo --== Generating Invalid Client Certificate ==-- 53 | keytool -genkeypair -keyalg RSA -keystore clientStore.jks -storepass 123456 -storetype JKS -alias notused 54 | 55 | keytool -list -keystore clientStore.jks -storepass 123456 -storetype JKS 56 | 57 | rm caKey.pem caCert.pem request.crs clientCert.crt -------------------------------------------------------------------------------- /src/main/java/com/notnoop/apns/internal/ReconnectPolicies.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.ReconnectPolicy; 34 | 35 | public final class ReconnectPolicies { 36 | 37 | public static class Never implements ReconnectPolicy { 38 | 39 | public boolean shouldReconnect() { return false; } 40 | public void reconnected() { } 41 | public Never copy() { return this; } 42 | } 43 | 44 | public static class Always implements ReconnectPolicy { 45 | public boolean shouldReconnect() { return true; } 46 | public void reconnected() { } 47 | public Always copy() { return this; } 48 | } 49 | 50 | public static class EveryHalfHour implements ReconnectPolicy { 51 | private static final long PERIOD = 30 * 60 * 1000; 52 | 53 | private long lastRunning = System.currentTimeMillis(); 54 | 55 | public boolean shouldReconnect() { 56 | return System.currentTimeMillis() - lastRunning > PERIOD; 57 | } 58 | 59 | public void reconnected() { 60 | lastRunning = System.currentTimeMillis(); 61 | } 62 | 63 | public EveryHalfHour copy() { 64 | return new EveryHalfHour(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/utils/Simulator/ApnsNotificationWithAction.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 com.notnoop.apns.utils.Simulator.ApnsServerSimulator.Notification; 34 | 35 | public class ApnsNotificationWithAction { 36 | private final Notification notification; 37 | private final ApnsResponse response; 38 | 39 | public ApnsNotificationWithAction(Notification notification) { 40 | this(notification, ApnsResponse.doNothing()); 41 | } 42 | 43 | public ApnsNotificationWithAction(Notification notification, ApnsResponse response) { 44 | if (notification == null) 45 | { 46 | throw new NullPointerException("notification cannot be null"); 47 | } 48 | this.notification = notification; 49 | if (response == null) 50 | { 51 | throw new NullPointerException("response cannot be null"); 52 | } 53 | this.response = response; 54 | } 55 | 56 | public Notification getNotification() { 57 | return notification; 58 | } 59 | 60 | public int getId() { 61 | return notification.getIdentifier(); 62 | } 63 | 64 | public ApnsResponse getResponse() { 65 | return response; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/notnoop/apns/ApnsNotification.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 | * Represents an APNS notification to be sent to Apple service. 35 | */ 36 | public interface ApnsNotification { 37 | 38 | /** 39 | * Returns the binary representation of the device token. 40 | */ 41 | public byte[] getDeviceToken(); 42 | 43 | /** 44 | * Returns the binary representation of the payload. 45 | * 46 | */ 47 | public byte[] getPayload(); 48 | 49 | /** 50 | * Returns the identifier of the current message. The 51 | * identifier is an application generated identifier. 52 | * 53 | * @return the notification identifier 54 | */ 55 | public int getIdentifier(); 56 | 57 | /** 58 | * Returns the expiry date of the notification, a fixed UNIX 59 | * epoch date expressed in seconds 60 | * 61 | * @return the expiry date of the notification 62 | */ 63 | public int getExpiry(); 64 | 65 | /** 66 | * Returns the binary representation of the message as expected by the 67 | * APNS server. 68 | * 69 | * The returned array can be used to sent directly to the APNS server 70 | * (on the wire/socket) without any modification. 71 | */ 72 | public byte[] marshall(); 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/internal/ApnsServiceImplTest.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 org.junit.Test; 34 | import static org.mockito.Mockito.*; 35 | 36 | import com.notnoop.apns.ApnsService; 37 | import com.notnoop.apns.EnhancedApnsNotification; 38 | 39 | public class ApnsServiceImplTest { 40 | 41 | EnhancedApnsNotification notification = new EnhancedApnsNotification(1, 42 | EnhancedApnsNotification.MAXIMUM_EXPIRY, "2342", "{}"); 43 | 44 | @Test 45 | public void pushEventually() { 46 | ApnsConnection connection = mock(ApnsConnection.class); 47 | ApnsService service = newService(connection, null); 48 | 49 | service.push(notification); 50 | 51 | verify(connection, times(1)).sendMessage(notification); 52 | } 53 | 54 | @Test 55 | public void pushEventuallySample() { 56 | ApnsConnection connection = mock(ApnsConnection.class); 57 | ApnsService service = newService(connection, null); 58 | 59 | service.push("2342", "{}"); 60 | 61 | verify(connection, times(1)).sendMessage(notification); 62 | } 63 | 64 | protected ApnsService newService(ApnsConnection connection, ApnsFeedbackConnection feedback) { 65 | return new ApnsServiceImpl(connection, null); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/notnoop/exceptions/InvalidSSLConfig.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.exceptions; 32 | 33 | import java.io.IOException; 34 | import java.security.KeyManagementException; 35 | import java.security.KeyStoreException; 36 | import java.security.NoSuchAlgorithmException; 37 | import java.security.UnrecoverableKeyException; 38 | import java.security.cert.CertificateException; 39 | 40 | /** 41 | * Signals that the the provided SSL context settings (e.g. 42 | * keystore path, password, encryption type, etc) are invalid 43 | * 44 | * This Exception can be caused by any of the following: 45 | * 46 | *
    47 | *
  1. {@link KeyStoreException}
  2. 48 | *
  3. {@link NoSuchAlgorithmException}
  4. 49 | *
  5. {@link CertificateException}
  6. 50 | *
  7. {@link IOException}
  8. 51 | *
  9. {@link UnrecoverableKeyException}
  10. 52 | *
  11. {@link KeyManagementException}
  12. 53 | *
54 | * 55 | */ 56 | public class InvalidSSLConfig extends ApnsException { 57 | private static final long serialVersionUID = -7283168775864517167L; 58 | 59 | public InvalidSSLConfig() { super(); } 60 | public InvalidSSLConfig(String message) { super(message); } 61 | public InvalidSSLConfig(Throwable cause) { super(cause); } 62 | public InvalidSSLConfig(String m, Throwable c) { super(m, c); } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/notnoop/apns/DeliveryError.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 | * Errors in delivery that may get reported by Apple APN servers 35 | */ 36 | public enum DeliveryError { 37 | /** 38 | * Connection closed without any error. 39 | * 40 | * This may occur if the APN service faces an invalid simple 41 | * APNS notification while running in enhanced mode 42 | */ 43 | NO_ERROR(0), 44 | PROCESSING_ERROR(1), 45 | MISSING_DEVICE_TOKEN(2), 46 | MISSING_TOPIC(3), 47 | MISSING_PAYLOAD(4), 48 | INVALID_TOKEN_SIZE(5), 49 | INVALID_TOPIC_SIZE(6), 50 | INVALID_PAYLOAD_SIZE(7), 51 | INVALID_TOKEN(8), 52 | 53 | NONE(255), 54 | UNKNOWN(254); 55 | 56 | private final byte code; 57 | DeliveryError(int code) { 58 | this.code = (byte)code; 59 | } 60 | 61 | /** The status code as specified by Apple */ 62 | public int code() { 63 | return code; 64 | } 65 | 66 | /** 67 | * Returns the appropriate {@code DeliveryError} enum 68 | * corresponding to the Apple provided status code 69 | * 70 | * @param code status code provided by Apple 71 | * @return the appropriate DeliveryError 72 | */ 73 | public static DeliveryError ofCode(int code) { 74 | for (DeliveryError e : DeliveryError.values()) { 75 | if (e.code == code) 76 | return e; 77 | } 78 | 79 | return UNKNOWN; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/ApnsFeedbackServerSocket.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.DataOutputStream; 34 | import java.io.IOException; 35 | import java.net.Socket; 36 | import java.util.Date; 37 | import java.util.Map; 38 | import java.util.Map.Entry; 39 | import java.util.concurrent.ExecutorService; 40 | 41 | import javax.net.ssl.SSLContext; 42 | 43 | /** 44 | * Represents the Apple Feedback server. This allows testing outside of the 45 | * Apple servers. 46 | */ 47 | public class ApnsFeedbackServerSocket extends AbstractApnsServerSocket { 48 | private final ApnsServerService requestDelegate; 49 | 50 | public ApnsFeedbackServerSocket(SSLContext sslContext, int port, 51 | ExecutorService executorService, ApnsServerService requestDelegate, 52 | ApnsServerExceptionDelegate exceptionDelegate) throws IOException { 53 | super(sslContext, port, executorService, exceptionDelegate); 54 | this.requestDelegate = requestDelegate; 55 | } 56 | 57 | @Override 58 | void handleSocket(Socket socket) throws IOException { 59 | Map inactiveDevices = requestDelegate 60 | .getInactiveDevices(); 61 | DataOutputStream dataStream = new DataOutputStream( 62 | socket.getOutputStream()); 63 | for (Entry entry : inactiveDevices.entrySet()) { 64 | int time = (int) (entry.getValue().getTime() / 1000L); 65 | dataStream.writeInt(time); 66 | byte[] bytes = entry.getKey(); 67 | dataStream.writeShort(bytes.length); 68 | dataStream.write(bytes); 69 | } 70 | dataStream.close(); 71 | } 72 | } -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/utils/junit/DumpThreadsOnErrorRule.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.junit; 32 | 33 | 34 | import org.junit.rules.TestRule; 35 | import org.junit.runner.Description; 36 | import org.junit.runners.model.Statement; 37 | 38 | import java.util.Map; 39 | 40 | public class DumpThreadsOnErrorRule implements TestRule { 41 | 42 | @Override 43 | public Statement apply(Statement base, Description description) { 44 | return new DumpThreadsStatement(base); 45 | } 46 | 47 | private static class DumpThreadsStatement extends Statement { 48 | 49 | private final Statement base; 50 | 51 | private DumpThreadsStatement(Statement base) { 52 | this.base = base; 53 | } 54 | 55 | @Override 56 | public void evaluate() throws Throwable { 57 | try { 58 | base.evaluate(); 59 | } catch (Throwable t) { 60 | dumpAllThreads(); 61 | throw t; 62 | } 63 | } 64 | 65 | private void dumpAllThreads() { 66 | Map liveThreads = Thread.getAllStackTraces(); 67 | for (Object o : liveThreads.keySet()) { 68 | Thread key = (Thread) o; 69 | System.err.println("\nThread " + key.getName()); 70 | StackTraceElement[] trace = (StackTraceElement[]) liveThreads.get(key); 71 | for (StackTraceElement aTrace : trace) { 72 | System.err.println("\tat " + aTrace); 73 | } 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /src/main/java/com/notnoop/exceptions/NetworkIOException.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.exceptions; 32 | 33 | import java.io.IOException; 34 | 35 | /** 36 | * Thrown to indicate that that a network operation has failed: 37 | * (e.g. connectivity problems, domain cannot be found, network 38 | * dropped). 39 | */ 40 | public class NetworkIOException extends ApnsException { 41 | private static final long serialVersionUID = 3353516625486306533L; 42 | 43 | private boolean resend; 44 | 45 | public NetworkIOException() { super(); } 46 | public NetworkIOException(String message) { super(message); } 47 | public NetworkIOException(IOException cause) { super(cause); } 48 | public NetworkIOException(String m, IOException c) { super(m, c); } 49 | public NetworkIOException(IOException cause, boolean resend) { 50 | super(cause); 51 | this.resend = resend; 52 | } 53 | 54 | /** 55 | * Identifies whether an exception was thrown during a resend of a 56 | * message or not. In this case a resend refers to whether the 57 | * message is being resent from the buffer of messages internal. 58 | * This would occur if we sent 5 messages quickly to APNS: 59 | * 1,2,3,4,5 and the 3 message was rejected. We would 60 | * then need to resend 4 and 5. If a network exception was 61 | * triggered when doing this, then the resend flag will be 62 | * {@code true}. 63 | * @return {@code true} for an exception trigger during a resend, otherwise {@code false}. 64 | */ 65 | public boolean isResend() { 66 | return resend; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/utils/Simulator/ApnsResponse.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 com.notnoop.apns.ApnsNotification; 34 | import com.notnoop.apns.DeliveryError; 35 | 36 | public class ApnsResponse { 37 | 38 | private final Action action; 39 | private final DeliveryError error; 40 | private final int errorId; 41 | 42 | private ApnsResponse(Action action, DeliveryError error, int errorId) { 43 | this.action = action; 44 | this.error = error; 45 | this.errorId = errorId; 46 | } 47 | 48 | public boolean isDoNothing() { 49 | return action == Action.DO_NOTHING; 50 | } 51 | 52 | public Action getAction() { 53 | return action; 54 | } 55 | 56 | public DeliveryError getError() { 57 | return error; 58 | } 59 | 60 | public int getErrorId() { 61 | return errorId; 62 | } 63 | 64 | public static ApnsResponse doNothing() { 65 | return new ApnsResponse(Action.DO_NOTHING, null, 0); 66 | } 67 | 68 | public static ApnsResponse returnError(DeliveryError error, int errorId) { 69 | return new ApnsResponse(Action.RETURN_ERROR, error, errorId); 70 | } 71 | 72 | public static ApnsResponse returnErrorAndShutdown(DeliveryError error, int errorId) { 73 | return new ApnsResponse(Action.RETURN_ERROR_AND_SHUTDOWN, error, errorId); 74 | } 75 | 76 | public static ApnsResponse returnErrorAndShutdown(DeliveryError error, ApnsNotification notification) { 77 | return new ApnsResponse(Action.RETURN_ERROR_AND_SHUTDOWN, error, notification.getIdentifier()); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/internal/SillyTests.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.Collections; 34 | import java.util.Date; 35 | import java.util.Map; 36 | 37 | import org.junit.Test; 38 | 39 | import static org.junit.Assert.assertEquals; 40 | import static org.mockito.Mockito.*; 41 | 42 | public class SillyTests { 43 | 44 | @Test 45 | public void testFeedbackCalls() { 46 | Map map = Collections.singletonMap("Test", new Date()); 47 | ApnsFeedbackConnection feed = mock(ApnsFeedbackConnection.class); 48 | when(feed.getInactiveDevices()).thenReturn(map); 49 | 50 | ApnsServiceImpl service = new ApnsServiceImpl(null, feed); 51 | assertEquals(map, service.getInactiveDevices()); 52 | 53 | // The feedback should be called once at most 54 | // Otherwise, we need to verify that the results of both 55 | // calls are accounted for 56 | verify(feed, times(1)).getInactiveDevices(); 57 | } 58 | 59 | @Test 60 | public void testQueuedFeedbackCalls() { 61 | Map map = Collections.singletonMap("Test", new Date()); 62 | ApnsFeedbackConnection feed = mock(ApnsFeedbackConnection.class); 63 | when(feed.getInactiveDevices()).thenReturn(map); 64 | 65 | ApnsServiceImpl service = new ApnsServiceImpl(null, feed); 66 | QueuedApnsService queued = new QueuedApnsService(service); 67 | assertEquals(map, queued.getInactiveDevices()); 68 | 69 | // The feedback should be called once at most 70 | // Otherwise, we need to verify that the results of both 71 | // calls are accounted for 72 | verify(feed, times(1)).getInactiveDevices(); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/internal/TlsTunnelBuilderTest.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.IOException; 35 | import java.io.InputStream; 36 | //import java.net.InetSocketAddress; 37 | //import java.net.Proxy; 38 | //import java.net.Socket; 39 | import org.junit.Test; 40 | import static org.junit.Assert.fail; 41 | 42 | public class TlsTunnelBuilderTest { 43 | 44 | @Test 45 | public void makeTunnelSuccess() throws IOException { 46 | /* Uncomment this test to verify with your proxy settings */ 47 | /*try { 48 | Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy.mydomain.com", 8080)); 49 | 50 | InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address(); 51 | Socket proxySocket = new Socket(proxyAddress.getAddress(), proxyAddress.getPort()); 52 | InetSocketAddress destAddress = new InetSocketAddress("myhost.com", 2195); 53 | 54 | new TlsTunnelBuilder().makeTunnel(destAddress.getAddress().toString(), 55 | destAddress.getPort(), 56 | "proxy-username", "proxy-password", 57 | proxyAddress); 58 | } catch (IOException ex){ 59 | fail(); 60 | }*/ 61 | 62 | } 63 | 64 | @Test 65 | public void invalidProxyParams() throws IOException { 66 | try { 67 | new TlsTunnelBuilder().makeTunnel("origin.example.com", 9876, null, null, null); 68 | fail(); 69 | } catch (IOException expected) { 70 | // No operation 71 | } 72 | } 73 | 74 | private InputStream inputStream(String content) throws IOException { 75 | return new ByteArrayInputStream(content.getBytes("UTF-8")); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/utils/StringTruncationTest.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 org.junit.Assert; 34 | 35 | import org.junit.experimental.theories.DataPoints; 36 | import org.junit.experimental.theories.Theories; 37 | import org.junit.experimental.theories.Theory; 38 | import org.junit.runner.RunWith; 39 | 40 | import com.notnoop.apns.internal.Utilities; 41 | 42 | // Test inspired by http://stackoverflow.com/questions/119328/how-do-i-truncate-a-java-string-to-fit-in-a-given-number-of-bytes-once-utf-8-enc 43 | @RunWith(Theories.class) 44 | public class StringTruncationTest { 45 | @DataPoints public static Object[][] dataPoints = { 46 | {"abcd", 0, 0}, 47 | {"abcd", 1, 1}, 48 | {"abcd", 2, 2}, 49 | {"abcd", 3, 3}, 50 | {"abcd", 4, 4}, 51 | {"abcd", 5, 4}, 52 | 53 | {"a\u0080b", 0, 0}, 54 | {"a\u0080b", 1, 1}, 55 | {"a\u0080b", 2, 1}, 56 | {"a\u0080b", 3, 3}, 57 | {"a\u0080b", 4, 4}, 58 | {"a\u0080b", 5, 4}, 59 | 60 | {"a\u0800b", 0, 0}, 61 | {"a\u0800b", 1, 1}, 62 | {"a\u0800b", 2, 1}, 63 | {"a\u0800b", 3, 1}, 64 | {"a\u0800b", 4, 4}, 65 | {"a\u0800b", 5, 5}, 66 | {"a\u0800b", 6, 5}, 67 | }; 68 | 69 | @DataPoints public static Object[][] surrogatePairs = { 70 | {"\uD834\uDD1E", 0, 0}, 71 | {"\uD834\uDD1E", 1, 0}, 72 | {"\uD834\uDD1E", 2, 0}, 73 | {"\uD834\uDD1E", 3, 0}, 74 | {"\uD834\uDD1E", 4, 4}, 75 | {"\uD834\uDD1E", 5, 4} 76 | }; 77 | 78 | @Theory 79 | public void truncateUTF8Properly(Object[] p) { 80 | String str = (String)p[0]; 81 | int maxBytes = (Integer)p[1]; 82 | int expectedBytes = (Integer)p[2]; 83 | 84 | String result = Utilities.truncateWhenUTF8(str, maxBytes); 85 | byte[] utf8 = Utilities.toUTF8Bytes(result); 86 | 87 | Assert.assertEquals(expectedBytes, utf8.length); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/internal/UtilitiesTest.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 org.junit.Assert; 34 | import org.junit.Test; 35 | 36 | public class UtilitiesTest { 37 | 38 | @Test 39 | public void testEncodeAndDecode() { 40 | String encodedHex = "a1b2d4"; 41 | 42 | byte[] decoded = Utilities.decodeHex(encodedHex); 43 | String encoded = Utilities.encodeHex(decoded); 44 | 45 | Assert.assertEquals(encodedHex.toLowerCase(), encoded.toLowerCase()); 46 | } 47 | 48 | @Test 49 | public void testParsingBytes() { 50 | Assert.assertEquals(0xFF00FF00, Utilities.parseBytes(0xFF, 0, 0xFF, 0)); 51 | Assert.assertEquals(0x00FF00FF, Utilities.parseBytes(0, 0xFF, 0, 0xFF)); 52 | Assert.assertEquals(0xDEADBEEF, Utilities.parseBytes(0xDE, 0xAD, 0xBE, 0xEF)); 53 | Assert.assertEquals(0x0EADBEEF, Utilities.parseBytes(0x0E, 0xAD, 0xBE, 0xEF)); 54 | 55 | Assert.assertTrue(Utilities.parseBytes(0xF0, 0,0,0) < 0); 56 | Assert.assertTrue(Utilities.parseBytes(0x80, 0,0,0) < 0); 57 | Assert.assertTrue(Utilities.parseBytes(0x70, 0,0,0) > 0); 58 | } 59 | 60 | @Test 61 | public void testEncodingUTF8() { 62 | String m = "esemény"; 63 | 64 | // See http://en.wikipedia.org/wiki/Unicode_equivalence#Example 65 | // 66 | // Oh the joy as different platforms choose to normalize Unicode differently ... but both are valid. 67 | // 68 | // This is intended to fix a problem under jdk 6, I was not able to reproduce it with jdk 7u51 on OSX Mavericks 69 | // (Java seems to also use expected_NFC here). 70 | byte[] expected_NFC = { 71 | 'e', 's', 'e', 'm', (byte)0x00C3, (byte)0x00A9, 'n', 'y' 72 | }; 73 | 74 | byte[] expected_NFD = { 75 | 'e', 's', 'e', 'm', (byte)0x00cc, (byte)0x0081, (byte)0x0061, 'n', 'y' 76 | }; 77 | 78 | final byte[] bytes = Utilities.toUTF8Bytes(m); 79 | 80 | if (bytes.length == 8) { 81 | Assert.assertArrayEquals(expected_NFC, bytes); 82 | } else { 83 | Assert.assertArrayEquals(expected_NFD, bytes); 84 | } 85 | 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/internal/QueuedApnsServiceTCTest.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.Test; 36 | import static org.mockito.Mockito.*; 37 | 38 | import com.notnoop.apns.ApnsService; 39 | import com.notnoop.apns.SimpleApnsNotification; 40 | import com.notnoop.apns.internal.QueuedApnsServiceTest.ConnectionStub; 41 | 42 | public class QueuedApnsServiceTCTest { 43 | 44 | @Test(expected=IllegalStateException.class) 45 | public void sendWithoutStarting() { 46 | QueuedApnsService service = new QueuedApnsService(null); 47 | service.push(notification); 48 | } 49 | 50 | SimpleApnsNotification notification = new SimpleApnsNotification("2342", "{}"); 51 | 52 | @Test 53 | public void pushEventually() { 54 | ConnectionStub connection = spy(new ConnectionStub(0, 1)); 55 | ApnsService service = newService(connection, null); 56 | 57 | service.push(notification); 58 | connection.semaphore.acquireUninterruptibly(); 59 | 60 | verify(connection, times(1)).sendMessage(notification); 61 | } 62 | 63 | @Test 64 | public void doNotBlock() { 65 | final int delay = 10000; 66 | ConnectionStub connection = spy(new ConnectionStub(delay, 2)); 67 | QueuedApnsService queued = 68 | new QueuedApnsService(new ApnsServiceImpl(connection, null)); 69 | queued.start(); 70 | long time1 = System.currentTimeMillis(); 71 | queued.push(notification); 72 | queued.push(notification); 73 | long time2 = System.currentTimeMillis(); 74 | assertTrue("queued.push() blocks", (time2 - time1) < delay); 75 | 76 | connection.interrupt(); 77 | connection.semaphore.acquireUninterruptibly(); 78 | verify(connection, times(2)).sendMessage(notification); 79 | 80 | queued.stop(); 81 | } 82 | 83 | protected ApnsService newService(ApnsConnection connection, ApnsFeedbackConnection feedback) { 84 | ApnsService service = new ApnsServiceImpl(connection, null); 85 | ApnsService queued = new QueuedApnsService(service); 86 | queued.start(); 87 | return queued; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/utils/Simulator/ApnsInputStream.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.ByteArrayInputStream; 34 | import java.io.DataInputStream; 35 | import java.io.IOException; 36 | import java.io.InputStream; 37 | import java.nio.ByteBuffer; 38 | 39 | public class ApnsInputStream extends DataInputStream { 40 | public ApnsInputStream(final InputStream inputStream) { 41 | super(inputStream); 42 | } 43 | 44 | byte[] readBlob() throws IOException { 45 | int length = readUnsignedShort(); 46 | byte[] blob = new byte[length]; 47 | readFully(blob); 48 | return blob; 49 | } 50 | 51 | ApnsInputStream readFrame() throws IOException { 52 | int length = readInt(); 53 | byte[] buffer = new byte[length]; 54 | readFully(buffer); 55 | final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(buffer); 56 | return new ApnsInputStream(byteArrayInputStream); 57 | } 58 | 59 | public Item readItem() throws IOException { 60 | byte itemId = readByte(); 61 | byte[] blob = readBlob(); 62 | return new Item(itemId, blob); 63 | } 64 | 65 | public static class Item { 66 | public final static byte ID_DEVICE_TOKEN = 1; 67 | public final static byte ID_PAYLOAD = 2; 68 | public final static byte ID_NOTIFICATION_IDENTIFIER = 3; 69 | public final static byte ID_EXPIRATION_DATE = 4; 70 | public final static byte ID_PRIORITY = 5; 71 | public final static Item DEFAULT = new Item((byte)0, new byte[0]); 72 | 73 | private final byte itemId; 74 | private final byte[] blob; 75 | 76 | public Item(final byte itemId, final byte[] blob) { 77 | 78 | this.itemId = itemId; 79 | this.blob = blob; 80 | } 81 | 82 | public byte getItemId() { 83 | return itemId; 84 | } 85 | 86 | public byte[] getBlob() { 87 | return blob.clone(); 88 | } 89 | 90 | public int getInt() { return blob.length < 4 ? 0 : ByteBuffer.wrap(blob).getInt(); } 91 | 92 | public byte getByte() { return blob.length < 1 ? 0 : blob[0]; } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/com/notnoop/apns/utils/Simulator/FailingApnsServerSimulator.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 org.slf4j.Logger; 34 | import org.slf4j.LoggerFactory; 35 | 36 | import javax.net.ServerSocketFactory; 37 | import java.io.IOException; 38 | import java.util.List; 39 | import java.util.concurrent.BlockingQueue; 40 | import java.util.concurrent.LinkedBlockingQueue; 41 | 42 | /** 43 | * A Server simulator that can simulate some failure modes. 44 | */ 45 | public class FailingApnsServerSimulator extends ApnsServerSimulator { 46 | 47 | private static final Logger logger = LoggerFactory.getLogger(FailingApnsServerSimulator.class); 48 | 49 | 50 | private BlockingQueue queue = new LinkedBlockingQueue(); 51 | 52 | public FailingApnsServerSimulator(final ServerSocketFactory sslFactory) { 53 | super(sslFactory); 54 | } 55 | 56 | @Override 57 | protected void onNotification(final ApnsServerSimulator.Notification notification, final InputOutputSocket inputOutputSocket) 58 | throws IOException { 59 | logger.debug("Queueing notification " + notification); 60 | queue.add(notification); 61 | final byte[] token = notification.getDeviceToken(); 62 | if (token.length == 32 && token[0] == (byte)0xff && token[1] == (byte)0xff) { 63 | switch (token[2]) { 64 | case 0: 65 | fail(token[3], notification.getIdentifier(), inputOutputSocket); 66 | break; 67 | 68 | case 1: 69 | try { 70 | final int millis = token[3] * 100; 71 | Thread.sleep(millis); 72 | } catch (InterruptedException e) { 73 | Thread.interrupted(); 74 | } 75 | break; 76 | 77 | case 2: 78 | default: 79 | inputOutputSocket.close(); 80 | break; 81 | } 82 | 83 | } 84 | } 85 | 86 | protected List getBadTokens() { 87 | return super.getBadTokens(); 88 | } 89 | 90 | public BlockingQueue 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 | --------------------------------------------------------------------------------