Browse Source

更换iOS推送为pushy

heavyrain2012 5 years ago
parent
commit
f543550c68
45 changed files with 326 additions and 4540 deletions
  1. 1 0
      .gitignore
  2. BIN
      apns/wfc.p12
  3. BIN
      apns/wfc_voip.p12
  4. BIN
      apns/wildfirechat_dev_push.p12
  5. BIN
      apns/wildfirechat_pro_push.p12
  6. BIN
      apns/wildfirechat_voip_push.p12
  7. 8 5
      config/apns.properties
  8. 21 1
      pom.xml
  9. 111 0
      push.iml
  10. 18 0
      push.log
  11. 9 0
      src/main/java/cn/wildfirechat/push/PushMessage.java
  12. 4 2
      src/main/java/cn/wildfirechat/push/android/hms/HMSPush.java
  13. 3 1
      src/main/java/cn/wildfirechat/push/android/xiaomi/XiaomiPush.java
  14. 19 28
      src/main/java/cn/wildfirechat/push/ios/ApnsConfig.java
  15. 129 80
      src/main/java/cn/wildfirechat/push/ios/ApnsServer.java
  16. 0 57
      src/main/java/com/notnoop/apns/APNS.java
  17. 0 92
      src/main/java/com/notnoop/apns/ApnsDelegate.java
  18. 0 52
      src/main/java/com/notnoop/apns/ApnsDelegateAdapter.java
  19. 0 76
      src/main/java/com/notnoop/apns/ApnsNotification.java
  20. 0 140
      src/main/java/com/notnoop/apns/ApnsService.java
  21. 0 760
      src/main/java/com/notnoop/apns/ApnsServiceBuilder.java
  22. 0 81
      src/main/java/com/notnoop/apns/DeliveryError.java
  23. 0 191
      src/main/java/com/notnoop/apns/EnhancedApnsNotification.java
  24. 0 535
      src/main/java/com/notnoop/apns/PayloadBuilder.java
  25. 0 118
      src/main/java/com/notnoop/apns/ReconnectPolicy.java
  26. 0 172
      src/main/java/com/notnoop/apns/SimpleApnsNotification.java
  27. 0 47
      src/main/java/com/notnoop/apns/StartSendingApnsDelegate.java
  28. 0 90
      src/main/java/com/notnoop/apns/internal/AbstractApnsService.java
  29. 0 52
      src/main/java/com/notnoop/apns/internal/ApnsConnection.java
  30. 0 412
      src/main/java/com/notnoop/apns/internal/ApnsConnectionImpl.java
  31. 0 121
      src/main/java/com/notnoop/apns/internal/ApnsFeedbackConnection.java
  32. 0 121
      src/main/java/com/notnoop/apns/internal/ApnsPooledConnection.java
  33. 0 59
      src/main/java/com/notnoop/apns/internal/ApnsServiceImpl.java
  34. 0 143
      src/main/java/com/notnoop/apns/internal/BatchApnsService.java
  35. 0 126
      src/main/java/com/notnoop/apns/internal/QueuedApnsService.java
  36. 0 67
      src/main/java/com/notnoop/apns/internal/ReconnectPolicies.java
  37. 0 179
      src/main/java/com/notnoop/apns/internal/SSLContextBuilder.java
  38. 0 147
      src/main/java/com/notnoop/apns/internal/TlsTunnelBuilder.java
  39. 0 296
      src/main/java/com/notnoop/apns/internal/Utilities.java
  40. 0 61
      src/main/java/com/notnoop/exceptions/ApnsDeliveryErrorException.java
  41. 0 44
      src/main/java/com/notnoop/exceptions/ApnsException.java
  42. 0 64
      src/main/java/com/notnoop/exceptions/InvalidSSLConfig.java
  43. 0 69
      src/main/java/com/notnoop/exceptions/NetworkIOException.java
  44. 0 50
      src/main/java/com/notnoop/exceptions/RuntimeIOException.java
  45. 3 1
      src/main/resources/application.properties

+ 1 - 0
.gitignore

@@ -1 +1,2 @@
 target
+.idea

BIN
apns/wfc.p12


BIN
apns/wfc_voip.p12


BIN
apns/wildfirechat_dev_push.p12


BIN
apns/wildfirechat_pro_push.p12


BIN
apns/wildfirechat_voip_push.p12


+ 8 - 5
config/apns.properties

@@ -1,9 +1,12 @@
-apns.product_cer_path=apns/wildfirechat_pro_push.p12
-apns.product_cer_pwd=123456
-apns.develop_cer_path=apns/wildfirechat_dev_push.p12
-apns.develop_cer_pwd=123456
-apns.voip_cer_path=apns/wildfirechat_voip_push.p12
+apns.cer_path=apns/wfc.p12
+apns.cer_pwd=123456
+apns.voip_cer_path=apns/wfc_voip.p12
 apns.voip_cer_pwd=123456
 
 apns.alert=default
 apns.voip_alert=ring.caf
+
+# 苹果要求使用voip推送必须使用callkit,不然会停掉voip推送。由于大陆政策,callkit被禁止,所以在大陆无法使用voip推送。
+# 苹果政策参考 https://developer.apple.com/documentation/pushkit/pkpushregistrydelegate/2875784-pushregistry?language=objc
+# On iOS 13.0 and later, if you fail to report a call to CallKit, the system will terminate your app. Repeatedly failing to report calls may cause the system to stop delivering any more VoIP push notifications to your app. If you want to initiate a VoIP call without using CallKit, register for push notifications using the UserNotifications framework instead of PushKit. For more information, see UserNotifications.
+apns.voip_feature=false

+ 21 - 1
pom.xml

@@ -132,7 +132,27 @@
 			<artifactId>jackson-annotations</artifactId>
 			<version>2.9.8</version>
 		</dependency>
-	</dependencies>
+
+
+		<dependency>
+			<groupId>com.turo</groupId>
+			<artifactId>pushy</artifactId>
+			<version>0.13.10</version>
+		</dependency>
+
+        <dependency>
+            <groupId>com.turo</groupId>
+            <artifactId>pushy-micrometer-metrics-listener</artifactId>
+            <version>0.13.10</version>
+        </dependency>
+
+<!--		   <dependency>-->
+<!--                <groupId>io.netty</groupId>-->
+<!--                <artifactId>netty-tcnative-boringssl-static</artifactId>-->
+<!--                <version>2.0.25.Final</version>-->
+<!--			    <scope>runtime</scope>-->
+<!--           </dependency>-->
+    </dependencies>
 
 	<build>
 		<plugins>

+ 111 - 0
push.iml

@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
+    <output url="file://$MODULE_DIR$/target/classes" />
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+      <excludeFolder url="file://$MODULE_DIR$/target" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework:spring-context:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-logging:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.3" level="project" />
+    <orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.3" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-to-slf4j:2.10.0" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.10.0" level="project" />
+    <orderEntry type="library" name="Maven: org.slf4j:jul-to-slf4j:1.7.25" level="project" />
+    <orderEntry type="library" name="Maven: javax.annotation:javax.annotation-api:1.3.2" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework:spring-core:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" scope="RUNTIME" name="Maven: org.yaml:snakeyaml:1.19" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-web:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-json:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.9.7" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.7" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.module:jackson-module-parameter-names:2.9.7" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-tomcat:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-core:8.5.34" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-el:8.5.34" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-websocket:8.5.34" level="project" />
+    <orderEntry type="library" name="Maven: org.hibernate.validator:hibernate-validator:6.0.13.Final" level="project" />
+    <orderEntry type="library" name="Maven: javax.validation:validation-api:2.0.1.Final" level="project" />
+    <orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.3.2.Final" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml:classmate:1.3.4" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework:spring-web:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework:spring-beans:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework:spring-webmvc:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework:spring-aop:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework:spring-expression:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-starter-test:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test-autoconfigure:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: com.jayway.jsonpath:json-path:2.4.0" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: net.minidev:json-smart:2.3" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: net.minidev:accessors-smart:1.2" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.ow2.asm:asm:5.0.4" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.12" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.assertj:assertj-core:3.9.1" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.mockito:mockito-core:2.15.0" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy:1.7.11" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy-agent:1.7.11" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.objenesis:objenesis:2.6" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-library:1.3" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.skyscreamer:jsonassert:1.5.0" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: com.vaadin.external.google:android-json:0.0.20131108.vaadin1" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.springframework:spring-test:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.xmlunit:xmlunit-core:2.5.1" level="project" />
+    <orderEntry type="module-library">
+      <library name="Maven: com.xiaomi.push:mipush-sdk-server:2.2.18">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/src/main/libs/MiPush_SDK_Server_2_2_19.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="library" name="Maven: com.meizu.flyme:push-server-sdk:1.2.7.20180307_release" level="project" />
+    <orderEntry type="library" name="Maven: com.alibaba:fastjson:1.1.28" level="project" />
+    <orderEntry type="library" name="Maven: com.google.code.gson:gson:2.8.2" level="project" />
+    <orderEntry type="library" name="Maven: commons-io:commons-io:2.5" level="project" />
+    <orderEntry type="library" name="Maven: com.googlecode.json-simple:json-simple:1.1.1" level="project" />
+    <orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.5" level="project" />
+    <orderEntry type="library" name="Maven: org.slf4j:slf4j-log4j12:1.7.5" level="project" />
+    <orderEntry type="library" name="Maven: log4j:log4j:1.2.17" level="project" />
+    <orderEntry type="library" name="Maven: commons-httpclient:commons-httpclient:3.1" level="project" />
+    <orderEntry type="library" name="Maven: commons-logging:commons-logging:1.0.4" level="project" />
+    <orderEntry type="library" name="Maven: commons-codec:commons-codec:1.11" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: uk.org.lidalia:slf4j-test:1.0.0-jdk6" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: uk.org.lidalia:lidalia-lang:1.0.0-jdk6" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.apache.commons:commons-lang3:3.7" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: com.google.guava:guava:14.0.1" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: uk.org.lidalia:lidalia-slf4j-ext:1.0.0-jdk6" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: joda-time:joda-time:2.9.9" level="project" />
+    <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:annotations:2.0.3" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.mockito:mockito-all:1.9.5" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.9.8" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.9.8" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.9.8" level="project" />
+    <orderEntry type="library" name="Maven: com.turo:pushy:0.13.10" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-codec-http2:4.1.29.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-codec-http:4.1.29.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-codec:4.1.29.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-handler:4.1.29.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-buffer:4.1.29.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-handler-proxy:4.1.29.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-transport:4.1.29.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-codec-socks:4.1.29.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-resolver-dns:4.1.29.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-resolver:4.1.29.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-common:4.1.29.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-codec-dns:4.1.29.Final" level="project" />
+    <orderEntry type="library" name="Maven: com.eatthepath:fast-uuid:0.1" level="project" />
+  </component>
+</module>

File diff suppressed because it is too large
+ 18 - 0
push.log


+ 9 - 0
src/main/java/cn/wildfirechat/push/PushMessage.java

@@ -20,6 +20,7 @@ public class PushMessage {
     public String packageName;
     public String deviceToken;
     public String voipDeviceToken;
+    public boolean isHiddenDetail;
     public String language;
 
 
@@ -151,6 +152,14 @@ public class PushMessage {
         this.voipDeviceToken = voipDeviceToken;
     }
 
+    public boolean isHiddenDetail() {
+        return isHiddenDetail;
+    }
+
+    public void setHiddenDetail(boolean hiddenDetail) {
+        isHiddenDetail = hiddenDetail;
+    }
+
     public String getLanguage() {
         return language;
     }

+ 4 - 2
src/main/java/cn/wildfirechat/push/android/hms/HMSPush.java

@@ -61,6 +61,8 @@ public class HMSPush {
 
         JSONObject msg = new JSONObject();
         msg.put("type", 1);//3: 通知栏消息,异步透传消息请根据接口文档设置
+        String token = pushMessage.getDeviceToken();
+        pushMessage.deviceToken = null;
         msg.put("body", new Gson().toJson(pushMessage));//通知栏消息body内容
 
         JSONObject hps = new JSONObject();//华为PUSH消息总结构体
@@ -82,10 +84,10 @@ public class HMSPush {
 
             String postUrl = apiUrl + "?nsp_ctx=" + URLEncoder.encode("{\"ver\":\"1\", \"appId\":\"" + mConfig.getAppId() + "\"}", "UTF-8");
             String response = httpPost(postUrl, postBody, 5000, 5000);
-            LOG.info("Push to {} response {}", pushMessage.getDeviceToken(), response);
+            LOG.info("Push to {} response {}", token, response);
         } catch (IOException e) {
             e.printStackTrace();
-            LOG.info("Push to {} with exception", pushMessage.getDeviceToken(), e);
+            LOG.info("Push to {} with exception", token, e);
         }
     }
 

+ 3 - 1
src/main/java/cn/wildfirechat/push/android/xiaomi/XiaomiPush.java

@@ -31,6 +31,8 @@ public class XiaomiPush {
         Sender sender = new Sender(mConfig.getAppSecret());
 
         Message message;
+        String token = pushMessage.getDeviceToken();
+        pushMessage.deviceToken = null;
         if(pushMessage.pushMessageType != PushMessageType.PUSH_MESSAGE_TYPE_NORMAL) {
             //voip
             long timeToLive = 60 * 1000; // 1 min
@@ -57,7 +59,7 @@ public class XiaomiPush {
 
         Result result = null;
         try {
-            result = sender.send(message, pushMessage.getDeviceToken(), 3);
+            result = sender.send(message, token, 3);
         } catch (IOException e) {
             e.printStackTrace();
         } catch (ParseException e) {

+ 19 - 28
src/main/java/cn/wildfirechat/push/ios/ApnsConfig.java

@@ -8,11 +8,8 @@ import org.springframework.context.annotation.PropertySource;
 @ConfigurationProperties(prefix="apns")
 @PropertySource(value = "file:config/apns.properties")
 public class ApnsConfig {
-    String productCerPath;
-    String productCerPwd;
-
-    String developCerPath;
-    String developCerPwd;
+    String cerPath;
+    String cerPwd;
 
     String voipCerPath;
     String voipCerPwd;
@@ -20,36 +17,22 @@ public class ApnsConfig {
     String alert;
     String voipAlert;
 
-    public String getProductCerPath() {
-        return productCerPath;
-    }
-
-    public void setProductCerPath(String productCerPath) {
-        this.productCerPath = productCerPath;
-    }
-
-    public String getProductCerPwd() {
-        return productCerPwd;
-    }
-
-    public void setProductCerPwd(String productCerPwd) {
-        this.productCerPwd = productCerPwd;
-    }
+    boolean voipFeature;
 
-    public String getDevelopCerPath() {
-        return developCerPath;
+    public String getCerPath() {
+        return cerPath;
     }
 
-    public void setDevelopCerPath(String developCerPath) {
-        this.developCerPath = developCerPath;
+    public void setCerPath(String cerPath) {
+        this.cerPath = cerPath;
     }
 
-    public String getDevelopCerPwd() {
-        return developCerPwd;
+    public String getCerPwd() {
+        return cerPwd;
     }
 
-    public void setDevelopCerPwd(String developCerPwd) {
-        this.developCerPwd = developCerPwd;
+    public void setCerPwd(String cerPwd) {
+        this.cerPwd = cerPwd;
     }
 
     public String getVoipCerPath() {
@@ -83,4 +66,12 @@ public class ApnsConfig {
     public void setVoipAlert(String voipAlert) {
         this.voipAlert = voipAlert;
     }
+
+    public boolean isVoipFeature() {
+        return voipFeature;
+    }
+
+    public void setVoipFeature(boolean voipFeature) {
+        this.voipFeature = voipFeature;
+    }
 }

+ 129 - 80
src/main/java/cn/wildfirechat/push/ios/ApnsServer.java

@@ -2,8 +2,14 @@ package cn.wildfirechat.push.ios;
 
 import cn.wildfirechat.push.PushMessage;
 import cn.wildfirechat.push.PushMessageType;
-import com.notnoop.apns.*;
-import com.notnoop.exceptions.ApnsDeliveryErrorException;
+import com.turo.pushy.apns.*;
+import com.turo.pushy.apns.metrics.micrometer.MicrometerApnsClientMetricsListener;
+import com.turo.pushy.apns.util.ApnsPayloadBuilder;
+import com.turo.pushy.apns.util.SimpleApnsPushNotification;
+import com.turo.pushy.apns.util.concurrent.PushNotificationFuture;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -11,57 +17,31 @@ import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
 
 import javax.annotation.PostConstruct;
-import java.util.Date;
-import java.util.Map;
+import java.io.File;
+import java.io.IOException;
+import java.util.Calendar;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
-import static com.notnoop.apns.DeliveryError.INVALID_TOKEN;
+import static java.lang.System.exit;
 
 @Component
-public class ApnsServer implements ApnsDelegate {
+public class ApnsServer  {
     private static final Logger LOG = LoggerFactory.getLogger(ApnsServer.class);
     private static ExecutorService mExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 5);
-    @Override
-    public void messageSent(ApnsNotification message, boolean resent) {
-        LOG.info("APNS push sent:{}", message.getDeviceToken());
-    }
 
-    @Override
-    public void messageSendFailed(ApnsNotification message, Throwable e) {
-        LOG.info("APNS push failure:{}", e.getMessage());
-        if(e instanceof ApnsDeliveryErrorException) {
-            ApnsDeliveryErrorException apnsDeliveryErrorException = (ApnsDeliveryErrorException)e;
-            LOG.info("APNS error code:{}", apnsDeliveryErrorException.getDeliveryError());
-            if (apnsDeliveryErrorException.getDeliveryError() == INVALID_TOKEN) {
-                if (message.getDeviceId() != null) {
-                    LOG.error("Invalide token!!!");
-                } else {
-                    LOG.error("APNS ERROR without deviceId:{}", message);
-                }
-            }
+    final SimpleMeterRegistry meterRegistry = new SimpleMeterRegistry();
+    final MicrometerApnsClientMetricsListener productMetricsListener =
+            new MicrometerApnsClientMetricsListener(meterRegistry,
+                    "notifications", "apns_product");
+    final MicrometerApnsClientMetricsListener developMetricsListener =
+            new MicrometerApnsClientMetricsListener(meterRegistry,
+                    "notifications", "apns_develop");
 
-        }
-    }
-
-    @Override
-    public void connectionClosed(DeliveryError e, int messageIdentifier) {
-        LOG.info("111");
-    }
-
-    @Override
-    public void cacheLengthExceeded(int newCacheLength) {
-        LOG.info("111");
-    }
-
-    @Override
-    public void notificationsResent(int resendCount) {
-        LOG.info("111");
-    }
-
-    ApnsService productSvc;
-    ApnsService developSvc;
-    ApnsService voipSvc;
+    ApnsClient productSvc;
+    ApnsClient developSvc;
+    ApnsClient productVoipSvc;
+    ApnsClient developVoipSvc;
 
     @Autowired
     private ApnsConfig mConfig;
@@ -76,29 +56,36 @@ public class ApnsServer implements ApnsDelegate {
             mConfig.alert = "default";
         }
 
-        productSvc = APNS.newService()
-                .asBatched(3, 10)
-                .withAppleDestination(true)
-                .withCert(mConfig.productCerPath, mConfig.productCerPwd)
-                .withDelegate(this)
-                .build();
-
-        developSvc = APNS.newService()
-                .asBatched(3, 10)
-                .withAppleDestination(false)
-                .withCert(mConfig.developCerPath, mConfig.developCerPwd)
-                .withDelegate(this)
-                .build();
-
-        voipSvc = APNS.newService()
-                .withAppleDestination(true)
-                .withCert(mConfig.voipCerPath, mConfig.voipCerPwd)
-                .withDelegate(this)
-                .build();
-
-        productSvc.start();
-        developSvc.start();
-        voipSvc.start();
+        try {
+            productSvc = new ApnsClientBuilder()
+                    .setApnsServer(ApnsClientBuilder.PRODUCTION_APNS_HOST)
+                    .setClientCredentials(new File(mConfig.cerPath), mConfig.cerPwd)
+                    .setMetricsListener(productMetricsListener)
+                    .build();
+
+            developSvc = new ApnsClientBuilder()
+                    .setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
+                    .setClientCredentials(new File(mConfig.cerPath), mConfig.cerPwd)
+                    .setMetricsListener(developMetricsListener)
+                    .build();
+
+            if (mConfig.voipFeature) {
+                productVoipSvc = new ApnsClientBuilder()
+                        .setApnsServer(ApnsClientBuilder.PRODUCTION_APNS_HOST)
+                        .setClientCredentials(new File(mConfig.voipCerPath), mConfig.voipCerPwd)
+                        .setMetricsListener(productMetricsListener)
+                        .build();
+                developSvc = new ApnsClientBuilder()
+                        .setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
+                        .setClientCredentials(new File(mConfig.voipCerPath), mConfig.voipCerPwd)
+                        .setMetricsListener(developMetricsListener)
+                        .build();
+            }
+
+        } catch (IOException e) {
+            e.printStackTrace();
+            exit(-1);
+        }
     }
 
 
@@ -110,12 +97,18 @@ public class ApnsServer implements ApnsDelegate {
                 LOG.error("等待太久,消息抛弃");
                 return;
             }
-            ApnsService service = developSvc;
+            ApnsClient service;
             if (pushMessage.getPushType() == IOSPushType.IOS_PUSH_TYPE_DISTRIBUTION) {
-                if (pushMessage.pushMessageType == PushMessageType.PUSH_MESSAGE_TYPE_NORMAL || StringUtils.isEmpty(pushMessage.getVoipDeviceToken())) {
+                if (!mConfig.voipFeature || pushMessage.pushMessageType == PushMessageType.PUSH_MESSAGE_TYPE_NORMAL || StringUtils.isEmpty(pushMessage.getVoipDeviceToken())) {
                     service = productSvc;
                 } else {
-                    service = voipSvc;
+                    service = productVoipSvc;
+                }
+            } else {
+                if (!mConfig.voipFeature || pushMessage.pushMessageType == PushMessageType.PUSH_MESSAGE_TYPE_NORMAL || StringUtils.isEmpty(pushMessage.getVoipDeviceToken())) {
+                    service = developSvc;
+                } else {
+                    service = developVoipSvc;
                 }
             }
 
@@ -127,12 +120,15 @@ public class ApnsServer implements ApnsDelegate {
             String sound = mConfig.alert;
 
             String pushContent = pushMessage.getPushContent();
+            boolean hiddenDetail = pushMessage.isHiddenDetail;
             if (pushMessage.pushMessageType == PushMessageType.PUSH_MESSAGE_TYPE_VOIP_INVITE) {
                 pushContent = "通话邀请";
                 sound = mConfig.voipAlert;
+                hiddenDetail = false;
             } else if(pushMessage.pushMessageType == PushMessageType.PUSH_MESSAGE_TYPE_VOIP_BYE) {
                 pushContent = "通话结束";
                 sound = null;
+                hiddenDetail = false;
             }
 
             int badge = pushMessage.getUnReceivedMsg();
@@ -160,6 +156,10 @@ public class ApnsServer implements ApnsDelegate {
                     body = pushMessage.senderName + ":" + pushContent;
                 }
 
+                if (hiddenDetail) {
+                    body = "你收到一条新消息"; //Todo 需要判断当前语言
+                }
+
                 if (pushMessage.mentionedType == 1) {
                     if (StringUtils.isEmpty(pushMessage.senderName)) {
                         body = "有人在群里@了你";
@@ -179,19 +179,68 @@ public class ApnsServer implements ApnsDelegate {
                 } else {
                     title = pushMessage.senderName;
                 }
-                body = pushContent;
+                if (hiddenDetail) {
+                    body = "你收到一条新消息"; //Todo 需要判断当前语言
+                } else {
+                    body = pushContent;
+                }
             }
 
-            final String payload = APNS.newPayload().alertBody(body).badge(badge).alertTitle(title).sound(sound).build();
-            final ApnsNotification goodMsg = service.push(service == voipSvc ? pushMessage.getVoipDeviceToken() : pushMessage.getDeviceToken(), payload, null);
-            LOG.info("Message id: " + goodMsg.getIdentifier());
+            final ApnsPayloadBuilder payloadBuilder = new ApnsPayloadBuilder();
+            payloadBuilder.setAlertBody(body);
+            payloadBuilder.setAlertTitle(title);
+            payloadBuilder.setBadgeNumber(badge);
+            payloadBuilder.setSound(sound);
 
-//
-//            //检查key到期日期
-//            final Map<String, Date> inactiveDevices = service.getInactiveDevices();
-//            for (final Map.Entry<String, Date> ent : inactiveDevices.entrySet()) {
-//                LOG.info("Inactive " + ent.getKey() + " at date " + ent.getValue());
-//            }
+            final String payload = payloadBuilder.buildWithDefaultMaximumLength();
+            final String token;
+            if (service == productVoipSvc || service == developVoipSvc) {
+                token = pushMessage.voipDeviceToken;
+            } else {
+                token = pushMessage.deviceToken;
+            }
+
+            Calendar c = Calendar.getInstance();
+
+
+            ApnsPushNotification pushNotification;
+
+            if (!mConfig.voipFeature || pushMessage.pushMessageType == PushMessageType.PUSH_MESSAGE_TYPE_NORMAL || StringUtils.isEmpty(pushMessage.getVoipDeviceToken())) {
+                if(pushMessage.pushMessageType == PushMessageType.PUSH_MESSAGE_TYPE_NORMAL || StringUtils.isEmpty(pushMessage.getVoipDeviceToken())) {
+                    c.add(Calendar.MINUTE, 10); //普通推送
+                    pushNotification = new SimpleApnsPushNotification(token, pushMessage.packageName, payload, c.getTime(), DeliveryPriority.CONSERVE_POWER, PushType.ALERT);
+                } else {
+                    c.add(Calendar.MINUTE, 1); //voip通知,使用普通推送
+                    pushNotification = new SimpleApnsPushNotification(token, pushMessage.packageName, payload, c.getTime(), DeliveryPriority.IMMEDIATE, PushType.ALERT);
+                }
+
+            } else {
+                c.add(Calendar.MINUTE, 1);
+                pushNotification = new SimpleApnsPushNotification(token, pushMessage.packageName + ".voip", payload, c.getTime(), DeliveryPriority.IMMEDIATE, PushType.VOIP);
+            }
+
+
+            final PushNotificationFuture<ApnsPushNotification, PushNotificationResponse<ApnsPushNotification>>
+                    sendNotificationFuture = service.sendNotification(pushNotification);
+            sendNotificationFuture.addListener(new GenericFutureListener<Future<? super PushNotificationResponse<ApnsPushNotification>>>() {
+                @Override
+                public void operationComplete(Future<? super PushNotificationResponse<ApnsPushNotification>> future) throws Exception {
+                    // When using a listener, callers should check for a failure to send a
+                    // notification by checking whether the future itself was successful
+                    // since an exception will not be thrown.
+                    if (future.isSuccess()) {
+                        final PushNotificationResponse<ApnsPushNotification> pushNotificationResponse =
+                                sendNotificationFuture.getNow();
+
+                        // Handle the push notification response as before from here.
+                    } else {
+                        // Something went wrong when trying to send the notification to the
+                        // APNs gateway. We can find the exception that caused the failure
+                        // by getting future.cause().
+                        future.cause().printStackTrace();
+                    }
+                }
+            });
         });
 
     }

+ 0 - 57
src/main/java/com/notnoop/apns/APNS.java

@@ -1,57 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns;
-
-/**
- * The main class to interact with the APNS Service.
- *
- * Provides an interface to create the {@link ApnsServiceBuilder} and
- * {@code ApnsNotification} payload.
- *
- */
-public final class APNS {
-
-    private APNS() { throw new AssertionError("Uninstantiable class"); }
-
-    /**
-     * Returns a new Payload builder
-     */
-    public static PayloadBuilder newPayload() {
-        return new PayloadBuilder();
-    }
-
-    /**
-     * Returns a new APNS Service for sending iPhone notifications
-     */
-    public static ApnsServiceBuilder newService() {
-        return new ApnsServiceBuilder();
-    }
-}

+ 0 - 92
src/main/java/com/notnoop/apns/ApnsDelegate.java

@@ -1,92 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns;
-
-/**
- * A delegate that gets notified of the status of notification delivery to the
- * Apple Server.
- *
- * The delegate doesn't get notified when the notification actually arrives at
- * the phone.
- */
-public interface ApnsDelegate {
-
-    /**
-     * Called when message was successfully sent to the Apple servers
-     *
-     * @param message the notification that was sent
-     * @param resent whether the notification was resent after an error
-     */
-    public void messageSent(ApnsNotification message, boolean resent);
-
-    /**
-     * Called when the delivery of the message failed for any reason
-     *
-     * If message is null, then your notification has been rejected by Apple but
-     * it has been removed from the cache so it is not possible to identify
-     * which notification caused the error. In this case subsequent
-     * notifications may be lost. If this happens you should consider increasing
-     * your cacheLength value to prevent data loss.
-     *
-     * @param message the notification that was attempted to be sent
-     * @param e the cause and description of the failure
-     */
-    public void messageSendFailed(ApnsNotification message, Throwable e);
-
-    /**
-     * The connection was closed and/or an error packet was received while
-     * monitoring was turned on.
-     *
-     * @param e the delivery error
-     * @param messageIdentifier  id of the message that failed
-     */
-    public void connectionClosed(DeliveryError e, int messageIdentifier);
-
-    /**
-     * The resend cache needed a bigger size (while resending messages)
-     *
-     * @param newCacheLength new size of the resend cache.
-     */
-    public void cacheLengthExceeded(int newCacheLength);
-
-    /**
-     * A number of notifications has been queued for resending due to a error-response
-     * packet being received.
-     *
-     * @param resendCount the number of messages being queued for resend
-     */
-    public void notificationsResent(int resendCount);
-    
-    /**
-     * A no operation delegate that does nothing!
-     */
-    public final static ApnsDelegate EMPTY = new ApnsDelegateAdapter();
-}

+ 0 - 52
src/main/java/com/notnoop/apns/ApnsDelegateAdapter.java

@@ -1,52 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns;
-
-/**
- * A no operation delegate that does nothing!
- */
-public class ApnsDelegateAdapter implements ApnsDelegate {
-
-    public void messageSent(ApnsNotification message, boolean resent) {
-    }
-
-    public void messageSendFailed(ApnsNotification message, Throwable e) {
-    }
-
-    public void connectionClosed(DeliveryError e, int messageIdentifier) {
-    }
-
-    public void cacheLengthExceeded(int newCacheLength) {
-    }
-
-    public void notificationsResent(int resendCount) {
-    }
-}

+ 0 - 76
src/main/java/com/notnoop/apns/ApnsNotification.java

@@ -1,76 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns;
-
-/**
- * Represents an APNS notification to be sent to Apple service.
- */
-public interface ApnsNotification {
-
-    /**
-     * Returns the binary representation of the device token.
-     */
-    public byte[] getDeviceToken();
-
-    /**
-     * Returns the binary representation of the payload.
-     *
-     */
-    public byte[] getPayload();
-
-    /**
-     * Returns the identifier of the current message.  The
-     * identifier is an application generated identifier.
-     *
-     * @return the notification identifier
-     */
-    public int getIdentifier();
-
-    /**
-     * Returns the expiry date of the notification, a fixed UNIX
-     * epoch date expressed in seconds
-     *
-     * @return the expiry date of the notification
-     */
-    public int getExpiry();
-
-    /**
-     * Returns the binary representation of the message as expected by the
-     * APNS server.
-     *
-     * The returned array can be used to sent directly to the APNS server
-     * (on the wire/socket) without any modification.
-     */
-    public byte[] marshall();
-
-
-    public String getDeviceId();
-}

+ 0 - 140
src/main/java/com/notnoop/apns/ApnsService.java

@@ -1,140 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns;
-
-import java.util.Collection;
-import java.util.Date;
-import java.util.Map;
-
-import com.notnoop.exceptions.NetworkIOException;
-
-/**
- * Represents the connection and interface to the Apple APNS servers.
- *
- * The service is created by {@link ApnsServiceBuilder} like:
- *
- * <pre>
- *   ApnsService = APNS.newService()
- *                  .withCert("/path/to/certificate.p12", "MyCertPassword")
- *                  .withSandboxDestination()
- *                  .build()
- * </pre>
- */
-public interface ApnsService {
-
-    /**
-     * Sends a push notification with the provided {@code payload} to the
-     * iPhone of {@code deviceToken}.
-     *
-     * The payload needs to be a valid JSON object, otherwise it may fail
-     * silently.  It is recommended to use {@link PayloadBuilder} to create
-     * one.
-     *
-     * @param deviceToken   the destination iPhone device token
-     * @param payload       The payload message
-     * @throws NetworkIOException if a network error occurred while
-     *      attempting to send the message
-     */
-    ApnsNotification push(String deviceToken, String payload, String deviceId) throws NetworkIOException;
-
-    EnhancedApnsNotification push(String deviceToken, String payload, Date expiry, String deviceId) throws NetworkIOException;
-
-    /**
-     * Sends a push notification with the provided {@code payload} to the
-     * iPhone of {@code deviceToken}.
-     *
-     * The payload needs to be a valid JSON object, otherwise it may fail
-     * silently.  It is recommended to use {@link PayloadBuilder} to create
-     * one.
-     *
-     * @param deviceToken   the destination iPhone device token
-     * @param payload       The payload message
-     * @throws NetworkIOException if a network error occurred while
-     *      attempting to send the message
-     */
-    ApnsNotification push(byte[] deviceToken, byte[] payload, String deviceId) throws NetworkIOException;
-
-    EnhancedApnsNotification push(byte[] deviceToken, byte[] payload, int expiry, String deviceId) throws NetworkIOException;
-
-    /**
-     * Sends the provided notification {@code message} to the desired
-     * destination.
-     * @throws NetworkIOException if a network error occurred while
-     *      attempting to send the message
-     */
-    void push(ApnsNotification message) throws NetworkIOException;
-
-    /**
-     * Starts the service.
-     *
-     * The underlying implementation may prepare its connections or
-     * data structures to be able to send the messages.
-     *
-     * This method is a blocking call, even if the service represents
-     * a Non-blocking push service.  Once the service is returned, it is ready
-     * to accept push requests.
-     *
-     * @throws NetworkIOException if a network error occurred while
-     *      starting the service
-     */
-    void start();
-
-    /**
-     * Stops the service and frees any allocated resources it created for this
-     * service.
-     *
-     * The underlying implementation should close all connections it created,
-     * and possibly stop any threads as well.
-     */
-    void stop();
-
-    /**
-     * Returns the list of devices that reported failed-delivery
-     * attempts to the Apple Feedback services.
-     *
-     * The result is map, mapping the device tokens as Hex Strings
-     * mapped to the timestamp when APNs determined that the
-     * application no longer exists on the device.
-     * @throws NetworkIOException if a network error occurred
-     *      while retrieving invalid device connection
-     */
-    Map<String, Date> getInactiveDevices() throws NetworkIOException;
-
-    /**
-     * Test that the service is setup properly and the Apple servers
-     * are reachable.
-     *
-     * @throws NetworkIOException   if the Apple servers aren't reachable
-     *      or the service cannot send notifications for now
-     */
-    void testConnection() throws NetworkIOException;
-    
-}

+ 0 - 760
src/main/java/com/notnoop/apns/ApnsServiceBuilder.java

@@ -1,760 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns;
-
-import com.notnoop.apns.internal.ApnsConnection;
-import com.notnoop.apns.internal.ApnsConnectionImpl;
-import com.notnoop.apns.internal.ApnsFeedbackConnection;
-import com.notnoop.apns.internal.ApnsPooledConnection;
-import com.notnoop.apns.internal.ApnsServiceImpl;
-import com.notnoop.apns.internal.BatchApnsService;
-import com.notnoop.apns.internal.QueuedApnsService;
-import com.notnoop.apns.internal.SSLContextBuilder;
-import com.notnoop.apns.internal.Utilities;
-import com.notnoop.exceptions.InvalidSSLConfig;
-import com.notnoop.exceptions.RuntimeIOException;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocketFactory;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.net.InetSocketAddress;
-import java.net.Proxy;
-import java.net.Socket;
-import java.security.KeyStore;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadFactory;
-
-import static com.notnoop.apns.internal.Utilities.PRODUCTION_FEEDBACK_HOST;
-import static com.notnoop.apns.internal.Utilities.PRODUCTION_FEEDBACK_PORT;
-import static com.notnoop.apns.internal.Utilities.PRODUCTION_GATEWAY_HOST;
-import static com.notnoop.apns.internal.Utilities.PRODUCTION_GATEWAY_PORT;
-import static com.notnoop.apns.internal.Utilities.SANDBOX_FEEDBACK_HOST;
-import static com.notnoop.apns.internal.Utilities.SANDBOX_FEEDBACK_PORT;
-import static com.notnoop.apns.internal.Utilities.SANDBOX_GATEWAY_HOST;
-import static com.notnoop.apns.internal.Utilities.SANDBOX_GATEWAY_PORT;
-import static java.util.concurrent.Executors.defaultThreadFactory;
-
-/**
- * The class is used to create instances of {@link ApnsService}.
- *
- * Note that this class is not synchronized.  If multiple threads access a
- * {@code ApnsServiceBuilder} instance concurrently, and at least on of the
- * threads modifies one of the attributes structurally, it must be
- * synchronized externally.
- *
- * Starting a new {@code ApnsService} is easy:
- *
- * <pre>
- *   ApnsService = APNS.newService()
- *    .withCert("/path/to/certificate.p12", "MyCertPassword")
- *    .withSandboxDestination()
- *    .build()
- * </pre>
- */
-public class ApnsServiceBuilder {
-    private static final String KEYSTORE_TYPE = "PKCS12";
-    private static final String KEY_ALGORITHM = ((java.security.Security.getProperty("ssl.KeyManagerFactory.algorithm") == null)? "sunx509" : java.security.Security.getProperty("ssl.KeyManagerFactory.algorithm"));
-
-    private SSLContext sslContext;
-
-    private int readTimeout;
-    private int connectTimeout;
-
-    private String gatewayHost;
-    private int gatewayPort = -1;
-
-    private String feedbackHost;
-    private int feedbackPort;
-    private int pooledMax = 1;
-    private int cacheLength = ApnsConnection.DEFAULT_CACHE_LENGTH;
-    private boolean autoAdjustCacheLength = true;
-    private ExecutorService executor;
-
-    private ReconnectPolicy reconnectPolicy = ReconnectPolicy.Provided.EVERY_HALF_HOUR.newObject();
-    private boolean isQueued;
-    private ThreadFactory queueThreadFactory;
-    
-    private boolean isBatched;
-    private int batchWaitTimeInSec;
-    private int batchMaxWaitTimeInSec;
-    private ScheduledExecutorService batchThreadPoolExecutor;
-    
-    private ApnsDelegate delegate = ApnsDelegate.EMPTY;
-    private Proxy proxy;
-    private String proxyUsername;
-    private String proxyPassword;
-    private boolean errorDetection = true;
-    private ThreadFactory errorDetectionThreadFactory;
-
-    /**
-     * Constructs a new instance of {@code ApnsServiceBuilder}
-     */
-    public ApnsServiceBuilder() { sslContext = null; }
-
-    /**
-     * Specify the certificate used to connect to Apple APNS
-     * servers.  This relies on the path (absolute or relative to
-     * working path) to the keystore (*.p12) containing the
-     * certificate, along with the given password.
-     *
-     * The keystore needs to be of PKCS12 and the keystore
-     * needs to be encrypted using the SunX509 algorithm.  Both
-     * of these settings are the default.
-     *
-     * This library does not support password-less p12 certificates, due to a
-     * Oracle Java library <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6415637">
-     * Bug 6415637</a>.  There are three workarounds: use a password-protected
-     * certificate, use a different boot Java SDK implementation, or construct
-     * the `SSLContext` yourself!  Needless to say, the password-protected
-     * certificate is most recommended option.
-     *
-     * @param fileName  the path to the certificate
-     * @param password  the password of the keystore
-     * @return  this
-     * @throws RuntimeIOException if it {@code fileName} cannot be
-     *          found or read
-     * @throws InvalidSSLConfig if fileName is invalid Keystore
-     *  or the password is invalid
-     */
-    public ApnsServiceBuilder withCert(String fileName, String password)
-    throws RuntimeIOException, InvalidSSLConfig {
-        FileInputStream stream = null;
-        try {
-            stream = new FileInputStream(fileName);
-            return withCert(stream, password);
-        } catch (FileNotFoundException e) {
-            throw new RuntimeIOException(e);
-        } finally {
-            Utilities.close(stream);
-        }
-    }
-
-    /**
-     * Specify the certificate used to connect to Apple APNS
-     * servers.  This relies on the stream of keystore (*.p12)
-     * containing the certificate, along with the given password.
-     *
-     * The keystore needs to be of PKCS12 and the keystore
-     * needs to be encrypted using the SunX509 algorithm.  Both
-     * of these settings are the default.
-     *
-     * This library does not support password-less p12 certificates, due to a
-     * Oracle Java library <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6415637">
-     * Bug 6415637</a>.  There are three workarounds: use a password-protected
-     * certificate, use a different boot Java SDK implementation, or constract
-     * the `SSLContext` yourself!  Needless to say, the password-protected
-     * certificate is most recommended option.
-     *
-     * @param stream    the keystore represented as input stream
-     * @param password  the password of the keystore
-     * @return  this
-     * @throws InvalidSSLConfig if stream is invalid Keystore
-     *  or the password is invalid
-     */
-    public ApnsServiceBuilder withCert(InputStream stream, String password)
-    throws InvalidSSLConfig {
-        assertPasswordNotEmpty(password);
-        return withSSLContext(new SSLContextBuilder()
-                .withAlgorithm(KEY_ALGORITHM)
-                .withCertificateKeyStore(stream, password, KEYSTORE_TYPE)
-                .withDefaultTrustKeyStore()
-                .build());
-    }
-
-    /**
-     * Specify the certificate used to connect to Apple APNS
-     * servers.  This relies on a keystore (*.p12)
-     * containing the certificate, along with the given password.
-     *
-     * This library does not support password-less p12 certificates, due to a
-     * Oracle Java library <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6415637">
-     * Bug 6415637</a>.  There are three workarounds: use a password-protected
-     * certificate, use a different boot Java SDK implementation, or construct
-     * the `SSLContext` yourself!  Needless to say, the password-protected
-     * certificate is most recommended option.
-     *
-     * @param keyStore  the keystore
-     * @param password  the password of the keystore
-     * @return  this
-     * @throws InvalidSSLConfig if stream is invalid Keystore
-     *  or the password is invalid
-     */
-    public ApnsServiceBuilder withCert(KeyStore keyStore, String password)
-    throws InvalidSSLConfig {
-        assertPasswordNotEmpty(password);
-        return withSSLContext(new SSLContextBuilder()
-                .withAlgorithm(KEY_ALGORITHM)
-                .withCertificateKeyStore(keyStore, password)
-                .withDefaultTrustKeyStore()
-                .build());
-    }
-
-    /**
-     * Specify the certificate store used to connect to Apple APNS
-     * servers.  This relies on the stream of keystore (*.p12 | *.jks)
-     * containing the keys and certificates, along with the given
-     * password and alias.
-     *
-     * The keystore can be either PKCS12 or JKS and the keystore
-     * needs to be encrypted using the SunX509 algorithm.
-     *
-     * This library does not support password-less p12 certificates, due to a
-     * Oracle Java library <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6415637">
-     * Bug 6415637</a>.  There are three workarounds: use a password-protected
-     * certificate, use a different boot Java SDK implementation, or constract
-     * the `SSLContext` yourself!  Needless to say, the password-protected
-     * certificate is most recommended option.
-     *
-     * @param stream    the keystore represented as input stream
-     * @param password  the password of the keystore
-     * @param alias     the alias identifing the key to be used
-     * @return  this
-     * @throws InvalidSSLConfig if stream is an invalid Keystore,
-     *  the password is invalid or the alias is not found
-     */
-    public ApnsServiceBuilder withCert(InputStream stream, String password, String alias)
-            throws InvalidSSLConfig {
-        assertPasswordNotEmpty(password);
-        return withSSLContext(new SSLContextBuilder()
-                .withAlgorithm(KEY_ALGORITHM)
-                .withCertificateKeyStore(stream, password, KEYSTORE_TYPE, alias)
-                .withDefaultTrustKeyStore()
-                .build());
-    }
-
-    /**
-     * Specify the certificate store used to connect to Apple APNS
-     * servers.  This relies on the stream of keystore (*.p12 | *.jks)
-     * containing the keys and certificates, along with the given
-     * password and alias.
-     *
-     * The keystore can be either PKCS12 or JKS and the keystore
-     * needs to be encrypted using the SunX509 algorithm.
-     *
-     * This library does not support password-less p12 certificates, due to a
-     * Oracle Java library <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6415637">
-     * Bug 6415637</a>.  There are three workarounds: use a password-protected
-     * certificate, use a different boot Java SDK implementation, or constract
-     * the `SSLContext` yourself!  Needless to say, the password-protected
-     * certificate is most recommended option.
-     *
-     * @param keyStore  the keystore
-     * @param password  the password of the keystore
-     * @param alias     the alias identifing the key to be used
-     * @return  this
-     * @throws InvalidSSLConfig if stream is an invalid Keystore,
-     *  the password is invalid or the alias is not found
-     */
-    public ApnsServiceBuilder withCert(KeyStore keyStore, String password, String alias)
-            throws InvalidSSLConfig {
-        assertPasswordNotEmpty(password);
-        return withSSLContext(new SSLContextBuilder()
-                .withAlgorithm(KEY_ALGORITHM)
-                .withCertificateKeyStore(keyStore, password, alias)
-                .withDefaultTrustKeyStore()
-                .build());
-    }
-
-	private void assertPasswordNotEmpty(String password) {
-		if (password == null || password.length() == 0) {
-            throw new IllegalArgumentException("Passwords must be specified." +
-                    "Oracle Java SDK does not support passwordless p12 certificates");
-        }
-	}
-    
-    /**
-     * Specify the SSLContext that should be used to initiate the
-     * connection to Apple Server.
-     *
-     * Most clients would use {@link #withCert(InputStream, String)}
-     * or {@link #withCert(String, String)} instead.  But some
-     * clients may need to represent the Keystore in a different
-     * format than supported.
-     *
-     * @param sslContext    Context to be used to create secure connections
-     * @return  this
-     */
-    public ApnsServiceBuilder withSSLContext(SSLContext sslContext) {
-        this.sslContext = sslContext;
-        return this;
-    }
-    
-    /**
-     * Specify the timeout value to be set in new setSoTimeout in created
-     * sockets, for both feedback and push connections, in milliseconds.
-     * @param readTimeout timeout value to be set in new setSoTimeout
-     * @return this
-     */
-    public ApnsServiceBuilder withReadTimeout(int readTimeout) {
-    	this.readTimeout = readTimeout;
-    	return this;
-    }
-
-    /**
-     * Specify the timeout value to use for connectionTimeout in created
-     * sockets, for both feedback and push connections, in milliseconds.
-     * @param connectTimeout timeout value to use for connectionTimeout
-     * @return this
-     */
-    public ApnsServiceBuilder withConnectTimeout(int connectTimeout) {
-    	this.connectTimeout = connectTimeout;
-    	return this;
-    }
-
-    /**
-     * Specify the gateway server for sending Apple iPhone
-     * notifications.
-     *
-     * Most clients should use {@link #withSandboxDestination()}
-     * or {@link #withProductionDestination()}.  Clients may use
-     * this method to connect to mocking tests and such.
-     *
-     * @param host  hostname the notification gateway of Apple
-     * @param port  port of the notification gateway of Apple
-     * @return  this
-     */
-    public ApnsServiceBuilder withGatewayDestination(String host, int port) {
-        this.gatewayHost = host;
-        this.gatewayPort = port;
-        return this;
-    }
-
-    /**
-     * Specify the Feedback for getting failed devices from
-     * Apple iPhone Push servers.
-     *
-     * Most clients should use {@link #withSandboxDestination()}
-     * or {@link #withProductionDestination()}.  Clients may use
-     * this method to connect to mocking tests and such.
-     *
-     * @param host  hostname of the feedback server of Apple
-     * @param port  port of the feedback server of Apple
-     * @return this
-     */
-    public ApnsServiceBuilder withFeedbackDestination(String host, int port) {
-        this.feedbackHost = host;
-        this.feedbackPort = port;
-        return this;
-    }
-
-    /**
-     * Specify to use Apple servers as iPhone gateway and feedback servers.
-     *
-     * If the passed {@code isProduction} is true, then it connects to the
-     * production servers, otherwise, it connects to the sandbox servers
-     *
-     * @param isProduction  determines which Apple servers should be used:
-     *               production or sandbox
-     * @return this
-     */
-    public ApnsServiceBuilder withAppleDestination(boolean isProduction) {
-        if (isProduction) {
-            return withProductionDestination();
-        } else {
-            return withSandboxDestination();
-        }
-    }
-
-    /**
-     * Specify to use the Apple sandbox servers as iPhone gateway
-     * and feedback servers.
-     *
-     * This is desired when in testing and pushing notifications
-     * with a development provision.
-     *
-     * @return  this
-     */
-    public ApnsServiceBuilder withSandboxDestination() {
-        return withGatewayDestination(SANDBOX_GATEWAY_HOST, SANDBOX_GATEWAY_PORT)
-        .withFeedbackDestination(SANDBOX_FEEDBACK_HOST, SANDBOX_FEEDBACK_PORT);
-    }
-
-    /**
-     * Specify to use the Apple Production servers as iPhone gateway
-     * and feedback servers.
-     *
-     * This is desired when sending notifications to devices with
-     * a production provision (whether through App Store or Ad hoc
-     * distribution).
-     *
-     * @return  this
-     */
-    public ApnsServiceBuilder withProductionDestination() {
-        return withGatewayDestination(PRODUCTION_GATEWAY_HOST, PRODUCTION_GATEWAY_PORT)
-        .withFeedbackDestination(PRODUCTION_FEEDBACK_HOST, PRODUCTION_FEEDBACK_PORT);
-    }
-
-    /**
-     * Specify the reconnection policy for the socket connection.
-     *
-     * Note: This option has no effect when using non-blocking
-     * connections.
-     */
-    public ApnsServiceBuilder withReconnectPolicy(ReconnectPolicy rp) {
-        this.reconnectPolicy = rp;
-        return this;
-    }
-    
-    /**
-     * Specify if the notification cache should auto adjust.
-     * Default is true
-     * 
-     * @param autoAdjustCacheLength the notification cache should auto adjust.
-     * @return this
-     */
-    public ApnsServiceBuilder withAutoAdjustCacheLength(boolean autoAdjustCacheLength) {
-        this.autoAdjustCacheLength = autoAdjustCacheLength;
-        return this;
-    }
-
-    /**
-     * Specify the reconnection policy for the socket connection.
-     *
-     * Note: This option has no effect when using non-blocking
-     * connections.
-     */
-    public ApnsServiceBuilder withReconnectPolicy(ReconnectPolicy.Provided rp) {
-        this.reconnectPolicy = rp.newObject();
-        return this;
-    }
-
-    /**
-     * Specify the address of the SOCKS proxy the connection should
-     * use.
-     *
-     * <p>Read the <a href="http://java.sun.com/javase/6/docs/technotes/guides/net/proxies.html">
-     * Java Networking and Proxies</a> guide to understand the
-     * proxies complexity.
-     *
-     * <p>Be aware that this method only handles SOCKS proxies, not
-     * HTTPS proxies.  Use {@link #withProxy(Proxy)} instead.
-     *
-     * @param host  the hostname of the SOCKS proxy
-     * @param port  the port of the SOCKS proxy server
-     * @return  this
-     */
-    public ApnsServiceBuilder withSocksProxy(String host, int port) {
-        Proxy proxy = new Proxy(Proxy.Type.SOCKS,
-                new InetSocketAddress(host, port));
-        return withProxy(proxy);
-    }
-
-    /**
-     * Specify the proxy and the authentication parameters to be used
-     * to establish the connections to Apple Servers.
-     *
-     * <p>Read the <a href="http://java.sun.com/javase/6/docs/technotes/guides/net/proxies.html">
-     * Java Networking and Proxies</a> guide to understand the
-     * proxies complexity.
-     *
-     * @param proxy the proxy object to be used to create connections
-     * @param proxyUsername a String object representing the username of the proxy server
-     * @param proxyPassword a String object representing the password of the proxy server
-     * @return  this
-     */
-    public ApnsServiceBuilder withAuthProxy(Proxy proxy, String proxyUsername, String proxyPassword) {
-        this.proxy = proxy;
-        this.proxyUsername = proxyUsername;
-        this.proxyPassword = proxyPassword;
-        return this;
-    }
-    
-    /**
-     * Specify the proxy to be used to establish the connections
-     * to Apple Servers
-     *
-     * <p>Read the <a href="http://java.sun.com/javase/6/docs/technotes/guides/net/proxies.html">
-     * Java Networking and Proxies</a> guide to understand the
-     * proxies complexity.
-     *
-     * @param proxy the proxy object to be used to create connections
-     * @return  this
-     */
-    public ApnsServiceBuilder withProxy(Proxy proxy) {
-        this.proxy = proxy;
-        return this;
-    }
-    
-    /**
-     * Specify the number of notifications to cache for error purposes.
-     * Default is 100
-     * 
-     * @param cacheLength  Number of notifications to cache for error purposes
-     * @return  this
-     */
-    public ApnsServiceBuilder withCacheLength(int cacheLength) {
-        this.cacheLength = cacheLength;
-        return this;
-    }
-
-    /**
-     * Specify the socket to be used as underlying socket to connect
-     * to the APN service.
-     *
-     * This assumes that the socket connects to a SOCKS proxy.
-     *
-     * @deprecated use {@link ApnsServiceBuilder#withProxy(Proxy)} instead
-     * @param proxySocket   the underlying socket for connections
-     * @return  this
-     */
-    @Deprecated
-    public ApnsServiceBuilder withProxySocket(Socket proxySocket) {
-        return this.withProxy(new Proxy(Proxy.Type.SOCKS,
-                proxySocket.getRemoteSocketAddress()));
-    }
-
-    /**
-     * Constructs a pool of connections to the notification servers.
-     *
-     * Apple servers recommend using a pooled connection up to
-     * 15 concurrent persistent connections to the gateways.
-     *
-     * Note: This option has no effect when using non-blocking
-     * connections.
-     */
-    public ApnsServiceBuilder asPool(int maxConnections) {
-        return asPool(Executors.newFixedThreadPool(maxConnections), maxConnections);
-    }
-
-    /**
-     * Constructs a pool of connections to the notification servers.
-     *
-     * Apple servers recommend using a pooled connection up to
-     * 15 concurrent persistent connections to the gateways.
-     *
-     * Note: This option has no effect when using non-blocking
-     * connections.
-     *
-     * Note: The maxConnections here is used as a hint to how many connections
-     * get created.
-     */
-    public ApnsServiceBuilder asPool(ExecutorService executor, int maxConnections) {
-        this.pooledMax = maxConnections;
-        this.executor = executor;
-        return this;
-    }
-
-    /**
-     * Constructs a new thread with a processing queue to process
-     * notification requests.
-     *
-     * @return  this
-     */
-    public ApnsServiceBuilder asQueued() {
-        return asQueued(Executors.defaultThreadFactory());
-    }
-    
-    /**
-     * Constructs a new thread with a processing queue to process
-     * notification requests.
-     *
-     * @param threadFactory
-     *            thread factory to use for queue processing
-     * @return  this
-     */
-    public ApnsServiceBuilder asQueued(ThreadFactory threadFactory) {
-        this.isQueued = true;
-        this.queueThreadFactory = threadFactory;
-        return this;
-    }
-    
-    /**
-     * Construct service which will process notification requests in batch.
-     * After each request batch will wait <code>waitTimeInSec (set as 5sec)</code> for more request to come
-     * before executing but not more than <code>maxWaitTimeInSec (set as 10sec)</code>
-     * 
-     * Note: It is not recommended to use pooled connection
-     */
-    public ApnsServiceBuilder asBatched() {
-        return asBatched(5, 10);
-    }
-    
-    /**
-     * Construct service which will process notification requests in batch.
-     * After each request batch will wait <code>waitTimeInSec</code> for more request to come
-     * before executing but not more than <code>maxWaitTimeInSec</code>
-     * 
-     * Note: It is not recommended to use pooled connection
-     * 
-     * @param waitTimeInSec
-     *            time to wait for more notification request before executing
-     *            batch
-     * @param maxWaitTimeInSec
-     *            maximum wait time for batch before executing
-     */
-    public ApnsServiceBuilder asBatched(int waitTimeInSec, int maxWaitTimeInSec) {
-        return asBatched(waitTimeInSec, maxWaitTimeInSec, (ThreadFactory)null);
-    }
-    
-    /**
-     * Construct service which will process notification requests in batch.
-     * After each request batch will wait <code>waitTimeInSec</code> for more request to come
-     * before executing but not more than <code>maxWaitTimeInSec</code>
-     * 
-     * Each batch creates new connection and close it after finished.
-     * In case reconnect policy is specified it will be applied by batch processing. 
-     * E.g.: {@link ReconnectPolicy.Provided#EVERY_HALF_HOUR} will reconnect the connection in case batch is running for more than half an hour
-     * 
-     * Note: It is not recommended to use pooled connection
-     * 
-     * @param waitTimeInSec
-     *            time to wait for more notification request before executing
-     *            batch
-     * @param maxWaitTimeInSec
-     *            maximum wait time for batch before executing
-     * @param threadFactory
-     *            thread factory to use for batch processing
-     */
-    public ApnsServiceBuilder asBatched(int waitTimeInSec, int maxWaitTimeInSec, ThreadFactory threadFactory) {
-        return asBatched(waitTimeInSec, maxWaitTimeInSec, new ScheduledThreadPoolExecutor(1, threadFactory != null ? threadFactory : defaultThreadFactory()));
-    }
-
-    /**
-     * Construct service which will process notification requests in batch.
-     * After each request batch will wait <code>waitTimeInSec</code> for more request to come
-     * before executing but not more than <code>maxWaitTimeInSec</code>
-     * 
-     * Each batch creates new connection and close it after finished.
-     * In case reconnect policy is specified it will be applied by batch processing. 
-     * E.g.: {@link ReconnectPolicy.Provided#EVERY_HALF_HOUR} will reconnect the connection in case batch is running for more than half an hour
-     * 
-     * Note: It is not recommended to use pooled connection
-     * 
-     * @param waitTimeInSec
-     *            time to wait for more notification request before executing
-     *            batch
-     * @param maxWaitTimeInSec
-     *            maximum wait time for batch before executing
-     * @param batchThreadPoolExecutor
-     *            executor for batched processing (may be null)
-     */
-    public ApnsServiceBuilder asBatched(int waitTimeInSec, int maxWaitTimeInSec, ScheduledExecutorService batchThreadPoolExecutor) {
-        this.isBatched = true;
-        this.batchWaitTimeInSec = waitTimeInSec;
-        this.batchMaxWaitTimeInSec = maxWaitTimeInSec;
-        this.batchThreadPoolExecutor = batchThreadPoolExecutor;
-        return this;
-    }
-
-    /**
-     * Sets the delegate of the service, that gets notified of the
-     * status of message delivery.
-     *
-     * Note: This option has no effect when using non-blocking
-     * connections.
-     */
-    public ApnsServiceBuilder withDelegate(ApnsDelegate delegate) {
-        this.delegate = delegate == null ? ApnsDelegate.EMPTY : delegate;
-        return this;
-    }
-
-    /**
-     * Disables the enhanced error detection, enabled by the
-     * enhanced push notification interface.  Error detection is
-     * enabled by default.
-     *
-     * This setting is desired when the application shouldn't spawn
-     * new threads.
-     *
-     * @return  this
-     */
-    public ApnsServiceBuilder withNoErrorDetection() {
-        this.errorDetection = false;
-        return this;
-    }
-
-    /**
-     * Provide a custom source for threads used for monitoring connections.
-     *
-     * This setting is desired when the application must obtain threads from a
-     * controlled environment Google App Engine. 
-     * @param threadFactory
-     *            thread factory to use for error detection
-     * @return  this
-     */
-    public ApnsServiceBuilder withErrorDetectionThreadFactory(ThreadFactory threadFactory) {
-        this.errorDetectionThreadFactory = threadFactory;
-        return this;
-    }
-
-    /**
-     * Returns a fully initialized instance of {@link ApnsService},
-     * according to the requested settings.
-     *
-     * @return  a new instance of ApnsService
-     */
-    public ApnsService build() {
-        checkInitialization();
-        ApnsService service;
-
-        SSLSocketFactory sslFactory = sslContext.getSocketFactory();
-        ApnsFeedbackConnection feedback = new ApnsFeedbackConnection(sslFactory, feedbackHost, feedbackPort, proxy, readTimeout, connectTimeout, proxyUsername, proxyPassword);
-
-        ApnsConnection conn = new ApnsConnectionImpl(sslFactory, gatewayHost,
-            gatewayPort, proxy, proxyUsername, proxyPassword, reconnectPolicy,
-                delegate, errorDetection, errorDetectionThreadFactory, cacheLength,
-                autoAdjustCacheLength, readTimeout, connectTimeout);
-        if (pooledMax != 1) {
-            conn = new ApnsPooledConnection(conn, pooledMax, executor);
-        }
-
-        service = new ApnsServiceImpl(conn, feedback);
-
-        if (isQueued) {
-            service = new QueuedApnsService(service, queueThreadFactory);
-        }
-        
-        if (isBatched) {
-            service = new BatchApnsService(conn, feedback, batchWaitTimeInSec, batchMaxWaitTimeInSec, batchThreadPoolExecutor);
-        }
-
-        service.start();
-
-        return service;
-    }
-
-    private void checkInitialization() {
-        if (sslContext == null)
-            throw new IllegalStateException(
-                    "SSL Certificates and attribute are not initialized\n"
-                    + "Use .withCert() methods.");
-        if (gatewayHost == null || gatewayPort == -1)
-            throw new IllegalStateException(
-                    "The Destination APNS server is not stated\n"
-                    + "Use .withDestination(), withSandboxDestination(), "
-                    + "or withProductionDestination().");
-    }
-}

+ 0 - 81
src/main/java/com/notnoop/apns/DeliveryError.java

@@ -1,81 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns;
-
-/**
- * Errors in delivery that may get reported by Apple APN servers
- */
-public enum DeliveryError {
-    /**
-     * Connection closed without any error.
-     *
-     * This may occur if the APN service faces an invalid simple
-     * APNS notification while running in enhanced mode
-     */
-    NO_ERROR(0),
-    PROCESSING_ERROR(1),
-    MISSING_DEVICE_TOKEN(2),
-    MISSING_TOPIC(3),
-    MISSING_PAYLOAD(4),
-    INVALID_TOKEN_SIZE(5),
-    INVALID_TOPIC_SIZE(6),
-    INVALID_PAYLOAD_SIZE(7),
-    INVALID_TOKEN(8),
-
-    NONE(255),
-    UNKNOWN(254);
-
-    private final byte code;
-    DeliveryError(int code) {
-        this.code = (byte)code;
-    }
-
-    /** The status code as specified by Apple */
-    public int code() {
-        return code;
-    }
-
-    /**
-     * Returns the appropriate {@code DeliveryError} enum
-     * corresponding to the Apple provided status code
-     *
-     * @param code  status code provided by Apple
-     * @return  the appropriate DeliveryError
-     */
-    public static DeliveryError ofCode(int code) {
-        for (DeliveryError e : DeliveryError.values()) {
-            if (e.code == code)
-                return e;
-        }
-
-        return UNKNOWN;
-    }
-}

+ 0 - 191
src/main/java/com/notnoop/apns/EnhancedApnsNotification.java

@@ -1,191 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns;
-
-import java.util.Arrays;
-import java.util.concurrent.atomic.AtomicInteger;
-import com.notnoop.apns.internal.Utilities;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import java.io.UnsupportedEncodingException;
-
-/**
- * Represents an APNS notification to be sent to Apple service.
- */
-public class EnhancedApnsNotification implements ApnsNotification {
-	
-    private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedApnsNotification.class);
-    private final static byte COMMAND = 1;
-    private static AtomicInteger nextId = new AtomicInteger(0);
-    private final int identifier;
-    private final int expiry;
-    private final byte[] deviceToken;
-    private final byte[] payload;
-    private String deviceId;
-
-    public void setDeviceId(String deviceId) {
-        this.deviceId = deviceId;
-    }
-
-    public static int INCREMENT_ID() {
-        return nextId.incrementAndGet();
-    }
-    
-    /**
-     * The infinite future for the purposes of Apple expiry date
-     */
-    public final static int MAXIMUM_EXPIRY = Integer.MAX_VALUE;
-
-    /**
-     * Constructs an instance of {@code ApnsNotification}.
-     *
-     * The message encodes the payload with a {@code UTF-8} encoding.
-     *
-     * @param dtoken    The Hex of the device token of the destination phone
-     * @param payload   The payload message to be sent
-     */
-    public EnhancedApnsNotification(
-            int identifier, int expiryTime,
-            String dtoken, String payload) {
-        this.identifier = identifier;
-        this.expiry = expiryTime;
-        this.deviceToken = Utilities.decodeHex(dtoken);
-        this.payload = Utilities.toUTF8Bytes(payload);
-    }
-
-    /**
-     * Constructs an instance of {@code ApnsNotification}.
-     *
-     * @param dtoken    The binary representation of the destination device token
-     * @param payload   The binary representation of the payload to be sent
-     */
-    public EnhancedApnsNotification(
-            int identifier, int expiryTime,
-            byte[] dtoken, byte[] payload) {
-        this.identifier = identifier;
-        this.expiry = expiryTime;
-        this.deviceToken = Utilities.copyOf(dtoken);
-        this.payload = Utilities.copyOf(payload);
-    }
-
-    /**
-     * Returns the binary representation of the device token.
-     *
-     */
-    public byte[] getDeviceToken() {
-        return Utilities.copyOf(deviceToken);
-    }
-
-    /**
-     * Returns the binary representation of the payload.
-     *
-     */
-    public byte[] getPayload() {
-        return Utilities.copyOf(payload);
-    }
-
-    public int getIdentifier() {
-        return identifier;
-    }
-
-    public int getExpiry() {
-        return expiry;
-    }
-
-    private byte[] marshall;
-    /**
-     * Returns the binary representation of the message as expected by the
-     * APNS server.
-     *
-     * The returned array can be used to sent directly to the APNS server
-     * (on the wire/socket) without any modification.
-     */
-    public byte[] marshall() {
-        if (marshall == null) {
-            marshall = Utilities.marshallEnhanced(COMMAND, identifier,
-                    expiry, deviceToken, payload);
-        }
-        return marshall.clone();
-    }
-
-    @Override
-    public String getDeviceId() {
-        return deviceId;
-    }
-
-    /**
-     * Returns the length of the message in bytes as it is encoded on the wire.
-     *
-     * Apple require the message to be of length 255 bytes or less.
-     *
-     * @return length of encoded message in bytes
-     */
-    public int length() {
-        int length = 1 + 4 + 4 + 2 + deviceToken.length + 2 + payload.length;
-        final int marshalledLength = marshall().length;
-        assert marshalledLength == length;
-        return length;
-    }
-
-    @Override
-    public int hashCode() {
-        return (21
-               + 31 * identifier
-               + 31 * expiry
-               + 31 * Arrays.hashCode(deviceToken)
-               + 31 * Arrays.hashCode(payload));
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof EnhancedApnsNotification))
-            return false;
-        EnhancedApnsNotification o = (EnhancedApnsNotification)obj;
-        return (identifier == o.identifier
-                && expiry == o.expiry
-                && Arrays.equals(this.deviceToken, o.deviceToken)
-                && Arrays.equals(this.payload, o.payload));
-    }
-
-    @Override
-    @SuppressFBWarnings("DE_MIGHT_IGNORE")
-    public String toString() {
-        String payloadString;
-        try {
-            payloadString = new String(payload, "UTF-8");
-        } catch (UnsupportedEncodingException ex) {
-            LOGGER.debug("UTF-8 charset not found on the JRE", ex);
-            payloadString = "???";
-        }
-        return "Message(Id="+identifier+"; Token="+Utilities.encodeHex(deviceToken)+"; Payload="+payloadString+")";
-    }
-}

+ 0 - 535
src/main/java/com/notnoop/apns/PayloadBuilder.java

@@ -1,535 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.notnoop.apns.internal.Utilities;
-
-/**
- * Represents a builder for constructing Payload requests, as
- * specified by Apple Push Notification Programming Guide.
- */
-public final class PayloadBuilder {
-    private static final ObjectMapper mapper = new ObjectMapper();
-
-    private final Map<String, Object> root;
-    private final Map<String, Object> aps;
-    private final Map<String, Object> customAlert;
-
-    /**
-     * Constructs a new instance of {@code PayloadBuilder}
-     */
-    PayloadBuilder() {
-        root = new HashMap<String, Object>();
-        aps = new HashMap<String, Object>();
-        customAlert = new HashMap<String, Object>();
-    }
-
-    /**
-     * Sets the alert body text, the text the appears to the user,
-     * to the passed value
-     *
-     * @param alert the text to appear to the user
-     * @return  this
-     */
-    public PayloadBuilder alertBody(final String alert) {
-        customAlert.put("body", alert);
-        return this;
-    }
-
-    /**
-     * Sets the alert title text, the text the appears to the user,
-     * to the passed value.
-     *
-     * Used on iOS 8.2, iWatch and also Safari
-     *
-     * @param title the text to appear to the user
-     * @return  this
-     */
-    public PayloadBuilder alertTitle(final String title) {
-        customAlert.put("title", title);
-        return this;
-    }
-
-    /**
-     * The key to a title string in the Localizable.strings file for the current localization.
-     *
-     * @param key  the localizable message title key
-     * @return  this
-     */
-    public PayloadBuilder localizedTitleKey(final String key) {
-        customAlert.put("title-loc-key", key);
-        return this;
-    }
-
-    /**
-     * Sets the arguments for the localizable title key.
-     *
-     * @param arguments the arguments to the localized alert message
-     * @return  this
-     */
-    public PayloadBuilder localizedTitleArguments(final Collection<String> arguments) {
-        customAlert.put("title-loc-args", arguments);
-        return this;
-    }
-
-    /**
-     * Sets the arguments for the localizable title key.
-     *
-     * @param arguments the arguments to the localized alert message
-     * @return  this
-     */
-    public PayloadBuilder localizedTitleArguments(final String... arguments) {
-        return localizedTitleArguments(Arrays.asList(arguments));
-    }
-
-    /**
-     * Sets the alert action text
-     *
-     * @param action The label of the action button
-     * @return  this
-     */
-    public PayloadBuilder alertAction(final String action) {
-        customAlert.put("action", action);
-        return this;
-    }
-
-    /**
-     * Sets the "url-args" key that are paired with the placeholders
-     * inside the urlFormatString value of your website.json file.
-     * The order of the placeholders in the URL format string determines
-     * the order of the values supplied by the url-args array.
-     *
-     * @param urlArgs the values to be paired with the placeholders inside
-     *                the urlFormatString value of your website.json file.
-     * @return  this
-     */
-    public PayloadBuilder urlArgs(final String... urlArgs){
-        aps.put("url-args", urlArgs);
-        return this;
-    }
-
-    /**
-     * Sets the alert sound to be played.
-     *
-     * Passing {@code null} disables the notification sound.
-     *
-     * @param sound the file name or song name to be played
-     *              when receiving the notification
-     * @return  this
-     */
-    public PayloadBuilder sound(final String sound) {
-        if (sound != null) {
-            aps.put("sound", sound);
-        } else {
-            aps.remove("sound");
-        }
-        return this;
-    }
-
-    /**
-     * Sets the category of the notification for iOS8 notification
-     * actions.  See 13 minutes into "What's new in iOS Notifications"
-     *
-     * Passing {@code null} removes the category.
-     *
-     * @param category the name of the category supplied to the app
-     *              when receiving the notification
-     * @return  this
-     */
-    public PayloadBuilder category(final String category) {
-        if (category != null) {
-            aps.put("category", category);
-        } else {
-            aps.remove("category");
-        }
-        return this;
-    }
-
-    /**
-     * Sets the notification badge to be displayed next to the
-     * application icon.
-     *
-     * The passed value is the value that should be displayed
-     * (it will be added to the previous badge number), and
-     * a badge of 0 clears the badge indicator.
-     *
-     * @param badge the badge number to be displayed
-     * @return  this
-     */
-    public PayloadBuilder badge(final int badge) {
-        aps.put("badge", badge);
-        return this;
-    }
-
-    /**
-     * Requests clearing of the badge number next to the application
-     * icon.
-     *
-     * This is an alias to {@code badge(0)}.
-     *
-     * @return this
-     */
-    public PayloadBuilder clearBadge() {
-        return badge(0);
-    }
-
-    /**
-     * Sets the value of action button (the right button to be
-     * displayed).  The default value is "View".
-     *
-     * The value can be either the simple String to be displayed or
-     * a localizable key, and the iPhone will show the appropriate
-     * localized message.
-     *
-     * A {@code null} actionKey indicates no additional button
-     * is displayed, just the Cancel button.
-     *
-     * @param actionKey the title of the additional button
-     * @return  this
-     */
-    public PayloadBuilder actionKey(final String actionKey) {
-        customAlert.put("action-loc-key", actionKey);
-        return this;
-    }
-
-    /**
-     * Set the notification view to display an action button.
-     *
-     * This is an alias to {@code actionKey(null)}
-     *
-     * @return this
-     */
-    public PayloadBuilder noActionButton() {
-        return actionKey(null);
-    }
-
-    /**
-     * Sets the notification type to be a 'newstand' notification.
-     *
-     * A Newstand Notification targets the Newstands app so that the app
-     * updates the subscription info and content.
-     *
-     * @return this
-     */
-    public PayloadBuilder forNewsstand() {
-        aps.put("content-available", 1);
-        return this;
-    }
-
-    /**
-     * With iOS7 it is possible to have the application wake up before the user opens the app.
-     * 
-     * The same key-word can also be used to send 'silent' notifications. With these 'silent' notification 
-     * a different app delegate is being invoked, allowing the app to perform background tasks.
-     *
-     * @return this
-     */
-    public PayloadBuilder instantDeliveryOrSilentNotification() {
-        aps.put("content-available", 1);
-        return this;
-    }
-
-    /**
-     * Set the notification localized key for the alert body
-     * message.
-     *
-     * @param key   the localizable message body key
-     * @return  this
-     */
-    public PayloadBuilder localizedKey(final String key) {
-        customAlert.put("loc-key", key);
-        return this;
-    }
-
-    /**
-     * Sets the arguments for the alert message localizable message.
-     *
-     * The iPhone doesn't localize the arguments.
-     *
-     * @param arguments the arguments to the localized alert message
-     * @return  this
-     */
-    public PayloadBuilder localizedArguments(final Collection<String> arguments) {
-        customAlert.put("loc-args", arguments);
-        return this;
-    }
-
-    /**
-     * Sets the arguments for the alert message localizable message.
-     *
-     * The iPhone doesn't localize the arguments.
-     *
-     * @param arguments the arguments to the localized alert message
-     * @return  this
-     */
-    public PayloadBuilder localizedArguments(final String... arguments) {
-        return localizedArguments(Arrays.asList(arguments));
-    }
-
-    /**
-     * Sets the launch image file for the push notification
-     *
-     * @param launchImage   the filename of the image file in the
-     *      application bundle.
-     * @return  this
-     */
-    public PayloadBuilder launchImage(final String launchImage) {
-        customAlert.put("launch-image", launchImage);
-        return this;
-    }
-
-    /**
-     * Sets any application-specific custom fields.  The values
-     * are presented to the application and the iPhone doesn't
-     * display them automatically.
-     *
-     * This can be used to pass specific values (urls, ids, etc) to
-     * the application in addition to the notification message
-     * itself.
-     *
-     * @param key   the custom field name
-     * @param value the custom field value
-     * @return  this
-     */
-    public PayloadBuilder customField(final String key, final Object value) {
-        root.put(key, value);
-        return this;
-    }
-
-    public PayloadBuilder mdm(final String s) {
-        return customField("mdm", s);
-    }
-
-    /**
-     * Set any application-specific custom fields.  These values
-     * are presented to the application and the iPhone doesn't
-     * display them automatically.
-     *
-     * This method *adds* the custom fields in the map to the
-     * payload, and subsequent calls add but doesn't reset the
-     * custom fields.
-     *
-     * @param values   the custom map
-     * @return  this
-     */
-    public PayloadBuilder customFields(final Map<String, ?> values) {
-        root.putAll(values);
-        return this;
-    }
-
-    /**
-     * Returns the length of payload bytes once marshaled to bytes
-     *
-     * @return the length of the payload
-     */
-    public int length() {
-        return copy().buildBytes().length;
-    }
-
-    /**
-     * Returns true if the payload built so far is larger than
-     * the size permitted by Apple (which is 2048 bytes).
-     *
-     * @return true if the result payload is too long
-     */
-    public boolean isTooLong() {
-        return length() > Utilities.MAX_PAYLOAD_LENGTH;
-    }
-
-    /**
-     * Shrinks the alert message body so that the resulting payload
-     * message fits within the passed expected payload length.
-     *
-     * This method performs best-effort approach, and its behavior
-     * is unspecified when handling alerts where the payload
-     * without body is already longer than the permitted size, or
-     * if the break occurs within word.
-     *
-     * @param payloadLength the expected max size of the payload
-     * @return  this
-     */
-    public PayloadBuilder resizeAlertBody(final int payloadLength) {
-        return resizeAlertBody(payloadLength, "");
-    }
-
-    /**
-     * Shrinks the alert message body so that the resulting payload
-     * message fits within the passed expected payload length.
-     *
-     * This method performs best-effort approach, and its behavior
-     * is unspecified when handling alerts where the payload
-     * without body is already longer than the permitted size, or
-     * if the break occurs within word.
-     *
-     * @param payloadLength the expected max size of the payload
-     * @param postfix for the truncated body, e.g. "..."
-     * @return  this
-     */
-    public PayloadBuilder resizeAlertBody(final int payloadLength, final String postfix) {
-        int currLength = length();
-        if (currLength <= payloadLength) {
-            return this;
-        }
-
-        // now we are sure that truncation is required
-        String body = (String)customAlert.get("body");
-
-        final int acceptableSize = Utilities.toUTF8Bytes(body).length
-                - (currLength - payloadLength
-                        + Utilities.toUTF8Bytes(postfix).length);
-        body = Utilities.truncateWhenUTF8(body, acceptableSize) + postfix;
-
-        // set it back
-        customAlert.put("body", body);
-
-        // calculate the length again
-        currLength = length();
-
-        if(currLength > payloadLength) {
-            // string is still too long, just remove the body as the body is
-            // anyway not the cause OR the postfix might be too long
-            customAlert.remove("body");
-        }
-
-        return this;
-    }
-
-    /**
-     * Shrinks the alert message body so that the resulting payload
-     * message fits within require Apple specification (2048 bytes).
-     *
-     * This method performs best-effort approach, and its behavior
-     * is unspecified when handling alerts where the payload
-     * without body is already longer than the permitted size, or
-     * if the break occurs within word.
-     *
-     * @return  this
-     */
-    public PayloadBuilder shrinkBody() {
-        return shrinkBody("");
-    }
-
-    /**
-     * Shrinks the alert message body so that the resulting payload
-     * message fits within require Apple specification (2048 bytes).
-     *
-     * This method performs best-effort approach, and its behavior
-     * is unspecified when handling alerts where the payload
-     * without body is already longer than the permitted size, or
-     * if the break occurs within word.
-     *
-     * @param postfix for the truncated body, e.g. "..."
-     *
-     * @return  this
-     */
-    public PayloadBuilder shrinkBody(final String postfix) {
-        return resizeAlertBody(Utilities.MAX_PAYLOAD_LENGTH, postfix);
-    }
-
-    /**
-     * Returns the JSON String representation of the payload
-     * according to Apple APNS specification
-     *
-     * @return  the String representation as expected by Apple
-     */
-    public String build() {
-        if (!root.containsKey("mdm")) {
-            insertCustomAlert();
-            root.put("aps", aps);
-        }
-        try {
-            return mapper.writeValueAsString(root);
-        } catch (final Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private void insertCustomAlert() {
-        switch (customAlert.size()) {
-            case 0:
-                aps.remove("alert");
-                break;
-            case 1:
-                if (customAlert.containsKey("body")) {
-                    aps.put("alert", customAlert.get("body"));
-                    break;
-                }
-                // else follow through
-                //$FALL-THROUGH$
-            default:
-                aps.put("alert", customAlert);
-        }
-    }
-
-    /**
-     * Returns the bytes representation of the payload according to
-     * Apple APNS specification
-     *
-     * @return the bytes as expected by Apple
-     */
-    public byte[] buildBytes() {
-        return Utilities.toUTF8Bytes(build());
-    }
-
-    @Override
-    public String toString() {
-        return build();
-    }
-
-    private PayloadBuilder(final Map<String, Object> root,
-            final Map<String, Object> aps,
-            final Map<String, Object> customAlert) {
-        this.root = new HashMap<String, Object>(root);
-        this.aps = new HashMap<String, Object>(aps);
-        this.customAlert = new HashMap<String, Object>(customAlert);
-    }
-
-    /**
-     * Returns a copy of this builder
-     *
-     * @return a copy of this builder
-     */
-    public PayloadBuilder copy() {
-        return new PayloadBuilder(root, aps, customAlert);
-    }
-
-    /**
-     * @return a new instance of Payload Builder
-     */
-    public static PayloadBuilder newPayload() {
-        return new PayloadBuilder();
-    }
-}

+ 0 - 118
src/main/java/com/notnoop/apns/ReconnectPolicy.java

@@ -1,118 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns;
-
-import com.notnoop.apns.internal.ReconnectPolicies;
-
-/**
- * Represents the reconnection policy for the library.
- *
- * Each object should be used exclusively for one
- * {@code ApnsService} only.
- */
-public interface ReconnectPolicy {
-    /**
-     * Returns {@code true} if the library should initiate a new
-     * connection for sending the message.
-     *
-     * The library calls this method at every message push.
-     *
-     * @return true if the library should be reconnected
-     */
-    public boolean shouldReconnect();
-
-    /**
-     * Callback method to be called whenever the library
-     * makes a new connection
-     */
-    public void reconnected();
-
-    /**
-     * Returns a deep copy of this reconnection policy, if needed.
-     *
-     * Subclasses may return this instance if the object is immutable.
-     */
-    public ReconnectPolicy copy();
-
-    /**
-     * Types of the library provided reconnection policies.
-     *
-     * This should capture most of the commonly used cases.
-     */
-    public enum Provided {
-        /**
-         * Only reconnect if absolutely needed, e.g. when the connection is dropped.
-         * <p>
-         * Apple recommends using a persistent connection.  This improves the latency of sending push notification messages.
-         * <p>
-         * The down-side is that once the connection is closed ungracefully (e.g. because Apple server drops it), the library wouldn't
-         * detect such failure and not warn against the messages sent after the drop before the detection.
-         */
-        NEVER {
-            @Override
-            public ReconnectPolicy newObject() {
-                return new ReconnectPolicies.Never();
-            }
-        },
-
-        /**
-         * Makes a new connection if the current connection has lasted for more than half an hour.
-         * <p>
-         * This is the recommended mode.
-         * <p>
-         * This is the sweat-spot in my experiments between dropped connections while minimizing latency.
-         */
-        EVERY_HALF_HOUR {
-            @Override
-            public ReconnectPolicy newObject() {
-                return new ReconnectPolicies.EveryHalfHour();
-            }
-        },
-
-        /**
-         * Makes a new connection for every message being sent.
-         *
-         * This option ensures that each message is actually
-         * delivered to Apple.
-         *
-         * If you send <strong>a lot</strong> of messages though,
-         * Apple may consider your requests to be a DoS attack.
-         */
-        EVERY_NOTIFICATION {
-            @Override
-            public ReconnectPolicy newObject() {
-                return new ReconnectPolicies.Always();
-            }
-        };
-
-        abstract ReconnectPolicy newObject();
-    }
-}

+ 0 - 172
src/main/java/com/notnoop/apns/SimpleApnsNotification.java

@@ -1,172 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns;
-
-import java.util.Arrays;
-
-import com.notnoop.apns.internal.Utilities;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import java.io.UnsupportedEncodingException;
-
-/**
- * Represents an APNS notification to be sent to Apple service. This is for legacy use only
- * and should not be used in new development.
- * https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/LegacyFormat.html
- *
- * This SimpleApnsNotification also only has limited error handling (by the APNS closing the connection
- * when a bad message was received) This prevents us from location the malformed notification.
- *
- * As push messages sent after a malformed notification are discarded by APNS messages will get lost
- * and not be delivered with the SimpleApnsNotification.
- *
- * @deprecated use EnhancedApnsNotification instead.
- */
-@SuppressWarnings("deprecation")
-@Deprecated
-public class SimpleApnsNotification implements ApnsNotification {
-	
-    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleApnsNotification.class);
-    private final static byte COMMAND = 0;
-    private final byte[] deviceToken;
-    private final byte[] payload;
-
-    /**
-     * Constructs an instance of {@code ApnsNotification}.
-     *
-     * The message encodes the payload with a {@code UTF-8} encoding.
-     *
-     * @param dtoken    The Hex of the device token of the destination phone
-     * @param payload   The payload message to be sent
-     */
-    public SimpleApnsNotification(String dtoken, String payload) {
-        this.deviceToken = Utilities.decodeHex(dtoken);
-        this.payload = Utilities.toUTF8Bytes(payload);
-    }
-
-    /**
-     * Constructs an instance of {@code ApnsNotification}.
-     *
-     * @param dtoken    The binary representation of the destination device token
-     * @param payload   The binary representation of the payload to be sent
-     */
-    public SimpleApnsNotification(byte[] dtoken, byte[] payload) {
-        this.deviceToken = Utilities.copyOf(dtoken);
-        this.payload = Utilities.copyOf(payload);
-    }
-
-    /**
-     * Returns the binary representation of the device token.
-     *
-     */
-    public byte[] getDeviceToken() {
-        return Utilities.copyOf(deviceToken);
-    }
-
-    /**
-     * Returns the binary representation of the payload.
-     *
-     */
-    public byte[] getPayload() {
-        return Utilities.copyOf(payload);
-    }
-
-    private byte[] marshall;
-    /**
-     * Returns the binary representation of the message as expected by the
-     * APNS server.
-     *
-     * The returned array can be used to sent directly to the APNS server
-     * (on the wire/socket) without any modification.
-     */
-    public byte[] marshall() {
-        if (marshall == null)
-            marshall = Utilities.marshall(COMMAND, deviceToken, payload);
-        return marshall.clone();
-    }
-
-    /**
-     * Returns the length of the message in bytes as it is encoded on the wire.
-     *
-     * Apple require the message to be of length 255 bytes or less.
-     *
-     * @return length of encoded message in bytes
-     */
-    public int length() {
-        int length = 1 + 2 + deviceToken.length + 2 + payload.length;
-        final int marshalledLength = marshall().length;
-        assert marshalledLength == length;
-        return length;
-    }
-
-    @Override
-    public int hashCode() {
-        return 21
-               + 31 * Arrays.hashCode(deviceToken)
-               + 31 * Arrays.hashCode(payload);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof SimpleApnsNotification))
-            return false;
-        SimpleApnsNotification o = (SimpleApnsNotification)obj;
-        return Arrays.equals(this.deviceToken, o.deviceToken)
-                && Arrays.equals(this.payload, o.payload);
-    }
-
-    public int getIdentifier() {
-        return -1;
-    }
-
-    public int getExpiry() {
-        return -1;
-    }
-    
-    @Override
-    @SuppressFBWarnings("DE_MIGHT_IGNORE")
-    public String toString() {
-        String payloadString;
-        try {
-            payloadString = new String(payload, "UTF-8");
-        } catch (UnsupportedEncodingException ex) {
-            LOGGER.debug("UTF-8 charset not found on the JRE", ex);
-            payloadString = "???";
-        }
-        return "Message(Token="+Utilities.encodeHex(deviceToken)+"; Payload="+payloadString+")";
-    }
-
-    @Override
-    public String getDeviceId() {
-        return null;
-    }
-}

+ 0 - 47
src/main/java/com/notnoop/apns/StartSendingApnsDelegate.java

@@ -1,47 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns;
-
-/**
- * A delegate that also gets notified just before a notification is being delivered to the
- * Apple Server.
- */
-public interface StartSendingApnsDelegate extends ApnsDelegate {
-
-    /**
-     * Called when message is about to be sent to the Apple servers.
-     *
-     * @param message the notification that is about to be sent
-     * @param resent whether the notification is being resent after an error
-     */
-    public void startSending(ApnsNotification message, boolean resent);
-
-}

+ 0 - 90
src/main/java/com/notnoop/apns/internal/AbstractApnsService.java

@@ -1,90 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns.internal;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import com.notnoop.apns.ApnsNotification;
-import com.notnoop.apns.ApnsService;
-import com.notnoop.apns.EnhancedApnsNotification;
-import com.notnoop.exceptions.NetworkIOException;
-
-abstract class AbstractApnsService implements ApnsService {
-    private ApnsFeedbackConnection feedback;
-    private AtomicInteger c = new AtomicInteger();
-
-    public AbstractApnsService(ApnsFeedbackConnection feedback) {
-        this.feedback = feedback;
-    }
-
-    public EnhancedApnsNotification push(String deviceToken, String payload, String deviceId) throws NetworkIOException {
-        EnhancedApnsNotification notification =
-            new EnhancedApnsNotification(c.incrementAndGet(), EnhancedApnsNotification.MAXIMUM_EXPIRY, deviceToken, payload);
-        notification.setDeviceId(deviceId);
-        push(notification);
-        return notification;
-    }
-
-    public EnhancedApnsNotification push(String deviceToken, String payload, Date expiry, String deviceId) throws NetworkIOException {
-        EnhancedApnsNotification notification =
-            new EnhancedApnsNotification(c.incrementAndGet(), (int)(expiry.getTime() / 1000), deviceToken, payload);
-        notification.setDeviceId(deviceId);
-        push(notification);
-        return notification;
-    }
-
-    public EnhancedApnsNotification push(byte[] deviceToken, byte[] payload, String deviceId) throws NetworkIOException {
-        EnhancedApnsNotification notification =
-            new EnhancedApnsNotification(c.incrementAndGet(), EnhancedApnsNotification.MAXIMUM_EXPIRY, deviceToken, payload);
-        notification.setDeviceId(deviceId);
-        push(notification);
-        return notification;
-    }
-
-    public EnhancedApnsNotification push(byte[] deviceToken, byte[] payload, int expiry, String deviceId) throws NetworkIOException {
-        EnhancedApnsNotification notification =
-            new EnhancedApnsNotification(c.incrementAndGet(), expiry, deviceToken, payload);
-        notification.setDeviceId(deviceId);
-        push(notification);
-        return notification;
-    }
-
-    public abstract void push(ApnsNotification message) throws NetworkIOException;
-
-    public Map<String, Date> getInactiveDevices() throws NetworkIOException {
-        return feedback.getInactiveDevices();
-    }
-}

+ 0 - 52
src/main/java/com/notnoop/apns/internal/ApnsConnection.java

@@ -1,52 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns.internal;
-
-import java.io.Closeable;
-
-import com.notnoop.apns.ApnsNotification;
-import com.notnoop.exceptions.NetworkIOException;
-
-public interface ApnsConnection extends Closeable {
-
-    //Default number of notifications to keep for error purposes
-    public static final int DEFAULT_CACHE_LENGTH = 100;
-
-    void sendMessage(ApnsNotification m) throws NetworkIOException;
-
-    void testConnection() throws NetworkIOException;
-
-    ApnsConnection copy();
-    
-    void setCacheLength(int cacheLength);
-    
-    int getCacheLength();
-}

+ 0 - 412
src/main/java/com/notnoop/apns/internal/ApnsConnectionImpl.java

@@ -1,412 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns.internal;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.InetSocketAddress;
-import java.net.Proxy;
-import java.net.Socket;
-import java.util.LinkedList;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.net.SocketFactory;
-import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.SSLSocketFactory;
-import com.notnoop.apns.ApnsDelegate;
-import com.notnoop.apns.StartSendingApnsDelegate;
-import com.notnoop.apns.ApnsNotification;
-import com.notnoop.apns.DeliveryError;
-import com.notnoop.apns.EnhancedApnsNotification;
-import com.notnoop.apns.ReconnectPolicy;
-import com.notnoop.exceptions.ApnsDeliveryErrorException;
-import com.notnoop.exceptions.NetworkIOException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class ApnsConnectionImpl implements ApnsConnection {
-
-    private static final Logger logger = LoggerFactory.getLogger(ApnsConnectionImpl.class);
-
-    private final SocketFactory factory;
-    private final String host;
-    private final int port;
-    private final int readTimeout;
-    private final int connectTimeout;
-    private final Proxy proxy;
-    private final String proxyUsername;
-    private final String proxyPassword;
-    private final ReconnectPolicy reconnectPolicy;
-    private final ApnsDelegate delegate;
-    private int cacheLength;
-    private final boolean errorDetection;
-    private final ThreadFactory threadFactory;
-    private final boolean autoAdjustCacheLength;
-    private final ConcurrentLinkedQueue<ApnsNotification> cachedNotifications, notificationsBuffer;
-    private Socket socket;
-    private final AtomicInteger threadId = new AtomicInteger(0);
-
-    public ApnsConnectionImpl(SocketFactory factory, String host, int port) {
-        this(factory, host, port, new ReconnectPolicies.Never(), ApnsDelegate.EMPTY);
-    }
-
-    private ApnsConnectionImpl(SocketFactory factory, String host, int port, ReconnectPolicy reconnectPolicy, ApnsDelegate delegate) {
-        this(factory, host, port, null, null, null, reconnectPolicy, delegate);
-    }
-
-    private ApnsConnectionImpl(SocketFactory factory, String host, int port, Proxy proxy, String proxyUsername, String proxyPassword,
-                               ReconnectPolicy reconnectPolicy, ApnsDelegate delegate) {
-        this(factory, host, port, proxy, proxyUsername, proxyPassword, reconnectPolicy, delegate, false, null,
-                ApnsConnection.DEFAULT_CACHE_LENGTH, true, 0, 0);
-    }
-
-    public ApnsConnectionImpl(SocketFactory factory, String host, int port, Proxy proxy, String proxyUsername, String proxyPassword,
-                              ReconnectPolicy reconnectPolicy, ApnsDelegate delegate, boolean errorDetection, ThreadFactory tf, int cacheLength,
-                              boolean autoAdjustCacheLength, int readTimeout, int connectTimeout) {
-        this.factory = factory;
-        this.host = host;
-        this.port = port;
-        this.reconnectPolicy = reconnectPolicy;
-        this.delegate = delegate == null ? ApnsDelegate.EMPTY : delegate;
-        this.proxy = proxy;
-        this.errorDetection = errorDetection;
-        this.threadFactory = tf == null ? defaultThreadFactory() : tf;
-        this.cacheLength = cacheLength;
-        this.autoAdjustCacheLength = autoAdjustCacheLength;
-        this.readTimeout = readTimeout;
-        this.connectTimeout = connectTimeout;
-        this.proxyUsername = proxyUsername;
-        this.proxyPassword = proxyPassword;
-        cachedNotifications = new ConcurrentLinkedQueue<ApnsNotification>();
-        notificationsBuffer = new ConcurrentLinkedQueue<ApnsNotification>();
-    }
-
-    private ThreadFactory defaultThreadFactory() {
-        return new ThreadFactory() {
-            ThreadFactory wrapped = Executors.defaultThreadFactory();
-            @Override
-            public Thread newThread( Runnable r )
-            {
-                Thread result = wrapped.newThread(r);
-                result.setName("MonitoringThread-"+threadId.incrementAndGet());
-                result.setDaemon(true);
-                return result;
-            }
-        };
-    }
-
-    public synchronized void close() {
-        Utilities.close(socket);
-    }
-
-    private void monitorSocket(final Socket socketToMonitor) {
-        logger.debug("Launching Monitoring Thread for socket {}", socketToMonitor);
-
-        Thread t = threadFactory.newThread(new Runnable() {
-            final static int EXPECTED_SIZE = 6;
-
-            @SuppressWarnings("InfiniteLoopStatement")
-            @Override
-            public void run() {
-                logger.debug("Started monitoring thread");
-                try {
-                    InputStream in;
-                    try {
-                        in = socketToMonitor.getInputStream();
-                    } catch (IOException ioe) {
-                        logger.warn("The value of socket is null", ioe);
-                        in = null;
-                    }
-
-                    byte[] bytes = new byte[EXPECTED_SIZE];
-                    while (in != null && readPacket(in, bytes)) {
-                        logger.debug("Error-response packet {}", Utilities.encodeHex(bytes));
-                        // Quickly close socket, so we won't ever try to send push notifications
-                        // using the defective socket.
-                        Utilities.close(socketToMonitor);
-
-                        int command = bytes[0] & 0xFF;
-                        if (command != 8) {
-                            throw new IOException("Unexpected command byte " + command);
-                        }
-                        int statusCode = bytes[1] & 0xFF;
-                        DeliveryError e = DeliveryError.ofCode(statusCode);
-
-                        int id = Utilities.parseBytes(bytes[2], bytes[3], bytes[4], bytes[5]);
-
-                        logger.debug("Closed connection cause={}; id={}", e, id);
-                        delegate.connectionClosed(e, id);
-
-                        Queue<ApnsNotification> tempCache = new LinkedList<ApnsNotification>();
-                        ApnsNotification notification = null;
-                        boolean foundNotification = false;
-
-                        while (!cachedNotifications.isEmpty()) {
-                            notification = cachedNotifications.poll();
-                            logger.debug("Candidate for removal, message id {}", notification.getIdentifier());
-
-                            if (notification.getIdentifier() == id) {
-                                logger.debug("Bad message found {}", notification.getIdentifier());
-                                foundNotification = true;
-                                break;
-                            }
-                            tempCache.add(notification);
-                        }
-
-                        if (foundNotification) {
-                            logger.debug("delegate.messageSendFailed, message id {}", notification.getIdentifier());
-                            delegate.messageSendFailed(notification, new ApnsDeliveryErrorException(e));
-                        } else {
-                            cachedNotifications.addAll(tempCache);
-                            int resendSize = tempCache.size();
-                            logger.warn("Received error for message that wasn't in the cache...");
-                            if (autoAdjustCacheLength) {
-                                cacheLength = cacheLength + (resendSize / 2);
-                                delegate.cacheLengthExceeded(cacheLength);
-                            }
-                            logger.debug("delegate.messageSendFailed, unknown id");
-                            delegate.messageSendFailed(null, new ApnsDeliveryErrorException(e));
-                        }
-
-                        int resendSize = 0;
-
-                        while (!cachedNotifications.isEmpty()) {
-
-                            resendSize++;
-                            final ApnsNotification resendNotification = cachedNotifications.poll();
-                            logger.debug("Queuing for resend {}", resendNotification.getIdentifier());
-                            notificationsBuffer.add(resendNotification);
-                        }
-                        logger.debug("resending {} notifications", resendSize);
-                        delegate.notificationsResent(resendSize);
-                    }
-                    logger.debug("Monitoring input stream closed by EOF");
-
-                } catch (IOException e) {
-                    // An exception when reading the error code is non-critical, it will cause another retry
-                    // sending the message. Other than providing a more stable network connection to the APNS
-                    // server we can't do much about it - so let's not spam the application's error log.
-                    logger.info("Exception while waiting for error code", e);
-                    delegate.connectionClosed(DeliveryError.UNKNOWN, -1);
-                } finally {
-                    Utilities.close(socketToMonitor);
-                    drainBuffer();
-                }
-            }
-
-            /**
-             * Read a packet like in.readFully(bytes) does - but do not throw an exception and return false if nothing
-             * could be read at all.
-             * @param in the input stream
-             * @param bytes the array to be filled with data
-             * @return true if a packet as been read, false if the stream was at EOF right at the beginning.
-             * @throws IOException When a problem occurs, especially EOFException when there's an EOF in the middle of the packet.
-             */
-            private boolean readPacket(final InputStream in, final byte[] bytes) throws IOException {
-                final int len = bytes.length;
-                int n = 0;
-                while (n < len) {
-                    try {
-                        int count = in.read(bytes, n, len - n);
-                        if (count < 0) {
-                            throw new EOFException("EOF after reading "+n+" bytes of new packet.");
-                        }
-                        n += count;
-                    } catch (IOException ioe) {
-                        if (n == 0)
-                            return false;
-                        throw new IOException("Error after reading "+n+" bytes of packet", ioe);
-                    }
-                }
-                return true;
-            }
-        });
-        t.start();
-    }
-
-    private synchronized Socket getOrCreateSocket(boolean resend) throws NetworkIOException {
-        if (reconnectPolicy.shouldReconnect()) {
-            logger.debug("Reconnecting due to reconnectPolicy dictating it");
-            Utilities.close(socket);
-            socket = null;
-        }
-
-        if (socket == null || socket.isClosed()) {
-            try {
-                if (proxy == null) {
-                    socket = factory.createSocket(host, port);
-                    logger.debug("Connected new socket {}", socket);
-                } else if (proxy.type() == Proxy.Type.HTTP) {
-                    TlsTunnelBuilder tunnelBuilder = new TlsTunnelBuilder();
-                    socket = tunnelBuilder.build((SSLSocketFactory) factory, proxy, proxyUsername, proxyPassword, host, port);
-                    logger.debug("Connected new socket through http tunnel {}", socket);
-                } else {
-                    boolean success = false;
-                    Socket proxySocket = null;
-                    try {
-                        proxySocket = new Socket(proxy);
-                        proxySocket.connect(new InetSocketAddress(host, port), connectTimeout);
-                        socket = ((SSLSocketFactory) factory).createSocket(proxySocket, host, port, false);
-                        success = true;
-                    } finally {
-                        if (!success) {
-                            Utilities.close(proxySocket);
-                        }
-                    }
-                    logger.debug("Connected new socket through socks tunnel {}", socket);
-                }
-
-                socket.setSoTimeout(readTimeout);
-                socket.setKeepAlive(true);
-
-                if (errorDetection) {
-                    monitorSocket(socket);
-                }
-
-                reconnectPolicy.reconnected();
-                logger.debug("Made a new connection to APNS");
-            } catch (IOException e) {
-                logger.error("Couldn't connect to APNS server", e);
-                // indicate to clients whether this is a resend or initial send
-                throw new NetworkIOException(e, resend);
-            }
-        }
-        return socket;
-    }
-
-    int DELAY_IN_MS = 1000;
-    private static final int RETRIES = 3;
-
-    public synchronized void sendMessage(ApnsNotification m) throws NetworkIOException {
-        sendMessage(m, false);
-        drainBuffer();
-    }
-
-    private synchronized void sendMessage(ApnsNotification m, boolean fromBuffer) throws NetworkIOException {
-        logger.debug("sendMessage {} fromBuffer: {}", m, fromBuffer);
-
-        if (delegate instanceof StartSendingApnsDelegate) {
-            ((StartSendingApnsDelegate) delegate).startSending(m, fromBuffer);
-        }
-
-        int attempts = 0;
-        while (true) {
-            try {
-                attempts++;
-                Socket socket = getOrCreateSocket(fromBuffer);
-                socket.getOutputStream().write(m.marshall());
-                socket.getOutputStream().flush();
-                cacheNotification(m);
-
-                delegate.messageSent(m, fromBuffer);
-
-                //logger.debug("Message \"{}\" sent", m);
-                attempts = 0;
-                break;
-            } catch (SSLHandshakeException e) {
-                // No use retrying this, it's dead Jim
-                throw new NetworkIOException(e);
-            } catch (IOException e) {
-                Utilities.close(socket);
-                if (attempts >= RETRIES) {
-                    logger.error("Couldn't send message after " + RETRIES + " retries." + m, e);
-                    delegate.messageSendFailed(m, e);
-                    Utilities.wrapAndThrowAsRuntimeException(e);
-                }
-                // The first failure might be due to closed connection (which in turn might be caused by
-                // a message containing a bad token), so don't delay for the first retry.
-                //
-                // Additionally we don't want to spam the log file in this case, only after the second retry
-                // which uses the delay.
-
-                if (attempts != 1) {
-                    logger.info("Failed to send message " + m + "... trying again after delay", e);
-                    Utilities.sleep(DELAY_IN_MS);
-                }
-            }
-        }
-    }
-
-    private synchronized void drainBuffer() {
-        logger.debug("draining buffer");
-        while (!notificationsBuffer.isEmpty()) {
-            final ApnsNotification notification = notificationsBuffer.poll();
-            try {
-                sendMessage(notification, true);
-            }
-            catch (NetworkIOException ex) {
-                // at this point we are retrying the submission of messages but failing to connect to APNS, therefore
-                // notify the client of this
-                delegate.messageSendFailed(notification, ex);
-            }
-        }
-    }
-
-    private void cacheNotification(ApnsNotification notification) {
-        cachedNotifications.add(notification);
-        while (cachedNotifications.size() > cacheLength) {
-            cachedNotifications.poll();
-            logger.debug("Removing notification from cache " + notification);
-        }
-    }
-
-    public ApnsConnectionImpl copy() {
-        return new ApnsConnectionImpl(factory, host, port, proxy, proxyUsername, proxyPassword, reconnectPolicy.copy(), delegate,
-                errorDetection, threadFactory, cacheLength, autoAdjustCacheLength, readTimeout, connectTimeout);
-    }
-
-    public void testConnection() throws NetworkIOException {
-        ApnsConnectionImpl testConnection = null;
-        try {
-            testConnection =
-                    new ApnsConnectionImpl(factory, host, port, proxy, proxyUsername, proxyPassword, reconnectPolicy.copy(), delegate);
-            final ApnsNotification notification = new EnhancedApnsNotification(0, 0, new byte[]{0}, new byte[]{0});
-            testConnection.sendMessage(notification);
-        } finally {
-            if (testConnection != null) {
-                testConnection.close();
-            }
-        }
-    }
-
-    public void setCacheLength(int cacheLength) {
-        this.cacheLength = cacheLength;
-    }
-
-    public int getCacheLength() {
-        return cacheLength;
-    }
-}

+ 0 - 121
src/main/java/com/notnoop/apns/internal/ApnsFeedbackConnection.java

@@ -1,121 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns.internal;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.InetSocketAddress;
-import java.net.Proxy;
-import java.net.Socket;
-import java.util.Date;
-import java.util.Map;
-import javax.net.SocketFactory;
-import javax.net.ssl.SSLSocketFactory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import com.notnoop.exceptions.NetworkIOException;
-
-public class ApnsFeedbackConnection {
-    private static final Logger logger = LoggerFactory.getLogger(ApnsFeedbackConnection.class);
-
-    private final SocketFactory factory;
-    private final String host;
-    private final int port;
-    private final Proxy proxy;
-    private final int readTimeout;
-    private final int connectTimeout;
-    private final String proxyUsername;
-    private final String proxyPassword;
-
-    public ApnsFeedbackConnection(final SocketFactory factory, final String host, final int port) {
-        this(factory, host, port, null, 0, 0, null, null);
-    }
-
-    public ApnsFeedbackConnection(final SocketFactory factory, final String host, final int port,
-            final Proxy proxy, int readTimeout, int connectTimeout, final String proxyUsername, final String proxyPassword) {
-        this.factory = factory;
-        this.host = host;
-        this.port = port;
-        this.proxy = proxy;
-        this.readTimeout = readTimeout;
-        this.connectTimeout = connectTimeout;
-        this.proxyUsername = proxyUsername;
-        this.proxyPassword = proxyPassword;
-    }
-
-    int DELAY_IN_MS = 1000;
-    private static final int RETRIES = 3;
-
-    public Map<String, Date> getInactiveDevices() throws NetworkIOException {
-        int attempts = 0;
-        while (true) {
-            try {
-                attempts++;
-                final Map<String, Date> result = getInactiveDevicesImpl();
-
-                attempts = 0;
-                return result;
-            } catch (final Exception e) {
-                logger.warn("Failed to retrieve invalid devices", e);
-                if (attempts >= RETRIES) {
-                    logger.error("Couldn't get feedback connection", e);
-                    Utilities.wrapAndThrowAsRuntimeException(e);
-                }
-                Utilities.sleep(DELAY_IN_MS);
-            }
-        }
-    }
-
-    public Map<String, Date> getInactiveDevicesImpl() throws IOException {
-        Socket proxySocket = null;
-        Socket socket = null;
-        try {
-            if (proxy == null) {
-                socket = factory.createSocket(host, port);
-            } else if (proxy.type() == Proxy.Type.HTTP) {
-                TlsTunnelBuilder tunnelBuilder = new TlsTunnelBuilder();
-                socket = tunnelBuilder.build((SSLSocketFactory) factory, proxy, proxyUsername, proxyPassword, host, port);
-            } else {
-                proxySocket = new Socket(proxy);
-                proxySocket.connect(new InetSocketAddress(host, port), connectTimeout);
-                socket = ((SSLSocketFactory) factory).createSocket(proxySocket, host, port, false);
-            }
-            socket.setSoTimeout(readTimeout);
-            socket.setKeepAlive(true);
-            final InputStream stream = socket.getInputStream();
-            return Utilities.parseFeedbackStream(stream);
-        } finally {
-            Utilities.close(socket);
-            Utilities.close(proxySocket);
-        }
-    }
-
-}

+ 0 - 121
src/main/java/com/notnoop/apns/internal/ApnsPooledConnection.java

@@ -1,121 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns.internal;
-
-import java.util.concurrent.*;
-import com.notnoop.apns.ApnsNotification;
-import com.notnoop.exceptions.NetworkIOException;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class ApnsPooledConnection implements ApnsConnection {
-    private static final Logger logger = LoggerFactory.getLogger(ApnsPooledConnection.class);
-
-    private final ApnsConnection prototype;
-    private final int max;
-
-    private final ExecutorService executors;
-    private final ConcurrentLinkedQueue<ApnsConnection> prototypes;
-
-    public ApnsPooledConnection(ApnsConnection prototype, int max) {
-        this(prototype, max, Executors.newFixedThreadPool(max));
-    }
-
-    public ApnsPooledConnection(ApnsConnection prototype, int max, ExecutorService executors) {
-        this.prototype = prototype;
-        this.max = max;
-
-        this.executors = executors;
-        this.prototypes = new ConcurrentLinkedQueue<ApnsConnection>();
-    }
-
-    private final ThreadLocal<ApnsConnection> uniquePrototype =
-        new ThreadLocal<ApnsConnection>() {
-        protected ApnsConnection initialValue() {
-            ApnsConnection newCopy = prototype.copy();
-            prototypes.add(newCopy);
-            return newCopy;
-        }
-    };
-
-    public void sendMessage(final ApnsNotification m) throws NetworkIOException {
-        Future<Void> future = executors.submit(new Callable<Void>() {
-            public Void call() throws Exception {
-                uniquePrototype.get().sendMessage(m);
-                return null;
-            }
-        });
-        try {
-            future.get();
-        } catch (InterruptedException ie) {
-            Thread.currentThread().interrupt();
-        } catch (ExecutionException ee) {
-            if (ee.getCause() instanceof NetworkIOException) {
-                throw (NetworkIOException) ee.getCause();
-            }
-        }
-    }
-
-    public ApnsConnection copy() {
-        // TODO: Should copy executor properly.... What should copy do
-        // really?!
-        return new ApnsPooledConnection(prototype, max);
-    }
-
-    public void close() {
-        executors.shutdown();
-        try {
-            executors.awaitTermination(10, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            logger.warn("pool termination interrupted", e);
-        }
-        for (ApnsConnection conn : prototypes) {
-            Utilities.close(conn);
-        }
-        Utilities.close(prototype);
-    }
-
-    public void testConnection() {
-        prototype.testConnection();
-    }
-
-    public synchronized void setCacheLength(int cacheLength) {  
-        for (ApnsConnection conn : prototypes) {
-            conn.setCacheLength(cacheLength);
-        }
-    }
-
-    @SuppressFBWarnings(value = "UG_SYNC_SET_UNSYNC_GET", justification = "prototypes is a MT-safe container")
-    public int getCacheLength() {
-        return prototypes.peek().getCacheLength();
-    }
-}

+ 0 - 59
src/main/java/com/notnoop/apns/internal/ApnsServiceImpl.java

@@ -1,59 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns.internal;
-
-import com.notnoop.apns.ApnsNotification;
-import com.notnoop.exceptions.NetworkIOException;
-
-public class ApnsServiceImpl extends AbstractApnsService {
-    private ApnsConnection connection;
-
-    public ApnsServiceImpl(ApnsConnection connection, ApnsFeedbackConnection feedback) {
-        super(feedback);
-        this.connection = connection;
-    }
-
-    @Override
-    public void push(ApnsNotification msg) throws NetworkIOException {
-        connection.sendMessage(msg);
-    }
-
-    public void start() {
-    }
-
-    public void stop() {
-        Utilities.close(connection);
-    }
-
-    public void testConnection() {
-        connection.testConnection();
-    }
-}

+ 0 - 143
src/main/java/com/notnoop/apns/internal/BatchApnsService.java

@@ -1,143 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns.internal;
-
-import static java.util.concurrent.Executors.defaultThreadFactory;
-
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
-
-import com.notnoop.apns.ApnsNotification;
-import com.notnoop.exceptions.NetworkIOException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class BatchApnsService extends AbstractApnsService {
-
-    private static final Logger logger = LoggerFactory.getLogger(BatchApnsService.class);
-
-	/**
-	 * How many seconds to wait for more messages before batch is send.
-	 * Each message reset the wait time
-	 * 
-	 * @see #maxBatchWaitTimeInSec
-	 */
-	private int batchWaitTimeInSec = 5;
-	
-	/**
-	 * How many seconds can be batch delayed before execution.
-	 * This time is not exact amount after which the batch will run its roughly the time
-	 */
-	private int maxBatchWaitTimeInSec = 10;
-	
-	private long firstMessageArrivedTime; 
-	
-	private ApnsConnection prototype;
-
-	private Queue<ApnsNotification> batch = new ConcurrentLinkedQueue<ApnsNotification>();
-
-	private ScheduledExecutorService scheduleService;
-	private ScheduledFuture<?> taskFuture;
-
-	private Runnable batchRunner = new SendMessagesBatch();
-
-    public BatchApnsService(ApnsConnection prototype, ApnsFeedbackConnection feedback, int batchWaitTimeInSec, int maxBachWaitTimeInSec, ThreadFactory tf) {
-        this(prototype, feedback, batchWaitTimeInSec, maxBachWaitTimeInSec,
-                new ScheduledThreadPoolExecutor(1,
-                        tf != null ? tf : defaultThreadFactory()));
-    }
-
-    public BatchApnsService(ApnsConnection prototype, ApnsFeedbackConnection feedback, int batchWaitTimeInSec, int maxBachWaitTimeInSec, ScheduledExecutorService executor) {
-		super(feedback);
-		this.prototype = prototype;
-		this.batchWaitTimeInSec = batchWaitTimeInSec;
-		this.maxBatchWaitTimeInSec = maxBachWaitTimeInSec;
-		this.scheduleService = executor != null ? executor : new ScheduledThreadPoolExecutor(1, defaultThreadFactory());
-	}
-
-	public void start() {
-		// no code
-	}
-
-	public void stop() {
-		Utilities.close(prototype);
-		if (taskFuture != null) {
-			taskFuture.cancel(true);
-		}
-		scheduleService.shutdownNow();
-	}
-
-	public void testConnection() throws NetworkIOException {
-		prototype.testConnection();
-	}
-
-	@Override
-	public void push(ApnsNotification message) throws NetworkIOException {
-		if (batch.isEmpty()) {
-			firstMessageArrivedTime = System.nanoTime();
-		}
-		
-		long sinceFirstMessageSec = (System.nanoTime() - firstMessageArrivedTime) / 1000 / 1000 / 1000;
-		
-		if (taskFuture != null && sinceFirstMessageSec < maxBatchWaitTimeInSec) {
-			taskFuture.cancel(false);
-		}
-		
-		batch.add(message);
-		
-		if (taskFuture == null || taskFuture.isDone()) {
-			taskFuture = scheduleService.schedule(batchRunner, batchWaitTimeInSec, TimeUnit.SECONDS);
-		}
-	}
-
-	class SendMessagesBatch implements Runnable {
-		public void run() {
-			ApnsConnection newConnection = prototype.copy();
-			try {
-				ApnsNotification msg;
-				while ((msg = batch.poll()) != null) {
-					try {
-						newConnection.sendMessage(msg);
-					} catch (NetworkIOException e) {
-                        logger.warn("Network exception sending message msg "+ msg.getIdentifier(), e);
-                    }
-				}
-			} finally {
-				Utilities.close(newConnection);
-			}
-		}
-	}
-}

+ 0 - 126
src/main/java/com/notnoop/apns/internal/QueuedApnsService.java

@@ -1,126 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns.internal;
-
-import java.util.Date;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Executors;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadFactory;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.notnoop.apns.ApnsNotification;
-import com.notnoop.apns.ApnsService;
-import com.notnoop.exceptions.NetworkIOException;
-
-public class QueuedApnsService extends AbstractApnsService {
-
-	private static final Logger logger = LoggerFactory.getLogger(QueuedApnsService.class);
-	
-    private ApnsService service;
-    private BlockingQueue<ApnsNotification> queue;
-    private AtomicBoolean started = new AtomicBoolean(false);
-
-    public QueuedApnsService(ApnsService service) {
-        this(service, null);
-    }
-
-    public QueuedApnsService(ApnsService service, final ThreadFactory tf) {
-        super(null);
-        this.service = service;
-        this.queue = new LinkedBlockingQueue<ApnsNotification>();
-        this.threadFactory = tf == null ? Executors.defaultThreadFactory() : tf;
-        this.thread = null;
-    }
-
-    @Override
-    public void push(ApnsNotification msg) {
-        if (!started.get()) {
-            throw new IllegalStateException("service hasn't be started or was closed");
-        }
-        queue.add(msg);
-    }
-
-    private final ThreadFactory threadFactory;
-    private Thread thread;
-    private volatile boolean shouldContinue;
-
-    public void start() {
-        if (started.getAndSet(true)) {
-            // I prefer if we throw a runtime IllegalStateException here,
-            // but I want to maintain semantic backward compatibility.
-            // So it is returning immediately here
-            return;
-        }
-
-        service.start();
-        shouldContinue = true;
-        thread = threadFactory.newThread(new Runnable() {
-            public void run() {
-                while (shouldContinue) {
-                    try {
-                        ApnsNotification msg = queue.take();
-                        service.push(msg);
-                    } catch (InterruptedException e) {
-                    	// ignore
-                    } catch (NetworkIOException e) {
-                    	// ignore: failed connect...
-                    } catch (Exception e) {
-                    	// weird if we reached here - something wrong is happening, but we shouldn't stop the service anyway!
-                    	logger.warn("Unexpected message caught... Shouldn't be here", e);
-                    }
-                }
-            }
-        });
-        thread.start();
-    }
-
-    public void stop() {
-        started.set(false);
-        shouldContinue = false;
-        thread.interrupt();
-        service.stop();
-    }
-
-    @Override
-    public Map<String, Date> getInactiveDevices() throws NetworkIOException {
-        return service.getInactiveDevices();
-    }
-
-    public void testConnection() throws NetworkIOException {
-        service.testConnection();
-    }
-
-}

+ 0 - 67
src/main/java/com/notnoop/apns/internal/ReconnectPolicies.java

@@ -1,67 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns.internal;
-
-import com.notnoop.apns.ReconnectPolicy;
-
-public final class ReconnectPolicies {
-
-    public static class Never implements ReconnectPolicy {
-
-        public boolean shouldReconnect() { return false; }
-        public void reconnected() { }
-        public Never copy() { return this; }
-    }
-
-    public static class Always implements ReconnectPolicy {
-        public boolean shouldReconnect() { return true; }
-        public void reconnected() { }
-        public Always copy() { return this; }
-    }
-
-    public static class EveryHalfHour implements ReconnectPolicy {
-        private static final long PERIOD = 30 * 60 * 1000;
-
-        private long lastRunning = System.currentTimeMillis();
-
-        public boolean shouldReconnect() {
-            return System.currentTimeMillis() - lastRunning > PERIOD;
-        }
-
-        public void reconnected() {
-            lastRunning = System.currentTimeMillis();
-        }
-
-        public EveryHalfHour copy() {
-            return new EveryHalfHour();
-        }
-    }
-}

+ 0 - 179
src/main/java/com/notnoop/apns/internal/SSLContextBuilder.java

@@ -1,179 +0,0 @@
-/*
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns.internal;
-
-import com.notnoop.exceptions.InvalidSSLConfig;
-
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.GeneralSecurityException;
-import java.security.Key;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-
-public class SSLContextBuilder {
-    private String algorithm = "sunx509";
-    private KeyManagerFactory keyManagerFactory;
-    private TrustManager[] trustManagers;
-
-    public SSLContextBuilder withAlgorithm(String algorithm) {
-        this.algorithm = algorithm;
-        return this;
-    }
-
-    public SSLContextBuilder withDefaultTrustKeyStore() throws InvalidSSLConfig {
-        try {
-            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(algorithm);
-            trustManagerFactory.init((KeyStore)null);
-            trustManagers = trustManagerFactory.getTrustManagers();
-            return this;
-        } catch (GeneralSecurityException e) {
-            throw new InvalidSSLConfig(e);
-        }
-    }
-
-    public SSLContextBuilder withTrustKeyStore(InputStream keyStoreStream, String keyStorePassword, String keyStoreType) throws InvalidSSLConfig {
-        try {
-            final KeyStore ks = KeyStore.getInstance(keyStoreType);
-            ks.load(keyStoreStream, keyStorePassword.toCharArray());
-            return withTrustKeyStore(ks, keyStorePassword);
-        } catch (GeneralSecurityException e) {
-            throw new InvalidSSLConfig(e);
-        } catch (IOException e) {
-            throw new InvalidSSLConfig(e);
-        }
-
-    }
-    public SSLContextBuilder withTrustKeyStore(KeyStore keyStore, String keyStorePassword) throws InvalidSSLConfig {
-        try {
-            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(algorithm);
-            trustManagerFactory.init(keyStore);
-            trustManagers = trustManagerFactory.getTrustManagers();
-            return this;
-        } catch (GeneralSecurityException e) {
-            throw new InvalidSSLConfig(e);
-        }
-    }
-
-    public SSLContextBuilder withTrustManager(TrustManager trustManager) {
-        trustManagers = new TrustManager[] { trustManager };
-        return this;
-    }
-
-    public SSLContextBuilder withCertificateKeyStore(InputStream keyStoreStream, String keyStorePassword, String keyStoreType) throws InvalidSSLConfig {
-        try {
-            final KeyStore ks = KeyStore.getInstance(keyStoreType);
-            ks.load(keyStoreStream, keyStorePassword.toCharArray());
-            return withCertificateKeyStore(ks, keyStorePassword);
-        } catch (GeneralSecurityException e) {
-            throw new InvalidSSLConfig(e);
-        } catch (IOException e) {
-            throw new InvalidSSLConfig(e);
-        }
-    }
-
-    public SSLContextBuilder withCertificateKeyStore(InputStream keyStoreStream, String keyStorePassword, String keyStoreType, String keyAlias) throws InvalidSSLConfig {
-        try {
-            final KeyStore ks = KeyStore.getInstance(keyStoreType);
-            ks.load(keyStoreStream, keyStorePassword.toCharArray());
-            return withCertificateKeyStore(ks, keyStorePassword, keyAlias);
-        } catch (GeneralSecurityException e) {
-            throw new InvalidSSLConfig(e);
-        } catch (IOException e) {
-            throw new InvalidSSLConfig(e);
-        }
-    }
-
-    public SSLContextBuilder withCertificateKeyStore(KeyStore keyStore, String keyStorePassword) throws InvalidSSLConfig {
-        try {
-            keyManagerFactory = KeyManagerFactory.getInstance(algorithm);
-            keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
-            return this;
-        } catch (GeneralSecurityException e) {
-            throw new InvalidSSLConfig(e);
-        }
-    }
-
-    public SSLContextBuilder withCertificateKeyStore(KeyStore keyStore, String keyStorePassword, String keyAlias) throws InvalidSSLConfig {
-        try {
-            if (!keyStore.containsAlias(keyAlias)) {
-                throw new InvalidSSLConfig("No key with alias " + keyAlias);
-            }
-            KeyStore singleKeyKeyStore = getKeyStoreWithSingleKey(keyStore, keyStorePassword, keyAlias);
-            return withCertificateKeyStore(singleKeyKeyStore, keyStorePassword);
-        } catch (GeneralSecurityException e) {
-            throw new InvalidSSLConfig(e);
-        } catch (IOException e) {
-            throw new InvalidSSLConfig(e);
-        }
-    }
-
-    /*
-     * Workaround for keystores containing multiple keys. Java will take the first key that matches
-     * and this way we can still offer configuration for a keystore with multiple keys and a selection
-     * based on alias. Also much easier than making a subclass of a KeyManagerFactory
-     */
-    private KeyStore getKeyStoreWithSingleKey(KeyStore keyStore, String keyStorePassword, String keyAlias)
-            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException {
-        KeyStore singleKeyKeyStore = KeyStore.getInstance(keyStore.getType(), keyStore.getProvider());
-        final char[] password = keyStorePassword.toCharArray();
-        singleKeyKeyStore.load(null, password);
-        Key key = keyStore.getKey(keyAlias, password);
-        Certificate[] chain = keyStore.getCertificateChain(keyAlias);
-        singleKeyKeyStore.setKeyEntry(keyAlias, key, password, chain);
-        return singleKeyKeyStore;
-    }
-
-    public SSLContext build() throws InvalidSSLConfig {
-        if (keyManagerFactory == null) {
-            throw new InvalidSSLConfig("Missing KeyManagerFactory");
-        }
-
-        if (trustManagers == null) {
-            throw new InvalidSSLConfig("Missing TrustManagers");
-        }
-
-        try {
-            final SSLContext sslContext = SSLContext.getInstance("TLS");
-            sslContext.init(keyManagerFactory.getKeyManagers(), trustManagers, null);
-            return sslContext;
-        } catch (GeneralSecurityException e) {
-            throw new InvalidSSLConfig(e);
-        }
-    }
-}

+ 0 - 147
src/main/java/com/notnoop/apns/internal/TlsTunnelBuilder.java

@@ -1,147 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns.internal;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.ProtocolException;
-import java.net.Proxy;
-import java.net.Socket;
-import javax.net.ssl.SSLSocketFactory;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import org.apache.commons.httpclient.ConnectMethod;
-import org.apache.commons.httpclient.NTCredentials;
-import org.apache.commons.httpclient.ProxyClient;
-import org.apache.commons.httpclient.UsernamePasswordCredentials;
-import org.apache.commons.httpclient.auth.AuthScope;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Establishes a TLS connection using an HTTP proxy. See <a
- * href="http://www.ietf.org/rfc/rfc2817.txt">RFC 2817 5.2</a>. This class does
- * not support proxies requiring a "Proxy-Authorization" header.
- */
-public final class TlsTunnelBuilder {
-    
-    private static final Logger logger = LoggerFactory.getLogger(TlsTunnelBuilder.class);
-    
-    public Socket build(SSLSocketFactory factory, Proxy proxy, String proxyUsername, String proxyPassword, String host, int port)
-            throws IOException {
-        boolean success = false;
-        Socket proxySocket = null;
-        try {
-            logger.debug("Attempting to use proxy : " + proxy.toString());
-            InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
-            proxySocket = makeTunnel(host, port, proxyUsername, proxyPassword, proxyAddress);
-
-            // Handshake with the origin server.
-            if(proxySocket ==  null) {
-                throw new ProtocolException("Unable to create tunnel through proxy server.");
-            }
-            Socket socket = factory.createSocket(proxySocket, host, port, true /* auto close */);
-            success = true;
-            return socket;
-        } finally {
-            if (!success) {
-                Utilities.close(proxySocket);
-            }
-        }
-    }
-
-    @SuppressFBWarnings(value = "VA_FORMAT_STRING_USES_NEWLINE",
-            justification = "use <CR><LF> as according to RFC, not platform-linefeed")
-    Socket makeTunnel(String host, int port, String proxyUsername, 
-            String proxyPassword, InetSocketAddress proxyAddress) throws IOException {
-        if(host == null || port < 0 || host.isEmpty() || proxyAddress == null){
-            throw new ProtocolException("Incorrect parameters to build tunnel.");   
-        }
-        logger.debug("Creating socket for Proxy : " + proxyAddress.getAddress() + ":" + proxyAddress.getPort());
-        Socket socket;
-        try {
-            ProxyClient client = new ProxyClient();
-            client.getParams().setParameter("http.useragent", "java-apns");
-            client.getHostConfiguration().setHost(host, port);
-            String proxyHost = proxyAddress.getAddress().toString().substring(0, proxyAddress.getAddress().toString().indexOf("/"));
-            client.getHostConfiguration().setProxy(proxyHost, proxyAddress.getPort());
-            
-        
-            ProxyClient.ConnectResponse response = client.connect();
-            socket = response.getSocket();
-            if (socket == null) {
-                ConnectMethod method = response.getConnectMethod();
-                // Read the proxy's HTTP response.
-                if(method.getStatusLine().getStatusCode() == 407) {
-                    // Proxy server returned 407. We will now try to connect with auth Header
-                    if(proxyUsername != null && proxyPassword != null) {
-                        socket = AuthenticateProxy(method, client,proxyHost, proxyAddress.getPort(),
-                                proxyUsername, proxyPassword);
-                    } else {
-                        throw new ProtocolException("Socket not created: " + method.getStatusLine()); 
-                    }
-                }             
-            }
-            
-        } catch (Exception e) {
-            throw new ProtocolException("Error occurred while creating proxy socket : " + e.toString());
-        }
-        if (socket != null) {
-            logger.debug("Socket for proxy created successfully : " + socket.getRemoteSocketAddress().toString());
-        }
-        return socket;
-    }
-    
-    private Socket AuthenticateProxy(ConnectMethod method, ProxyClient client, 
-            String proxyHost, int proxyPort, 
-            String proxyUsername, String proxyPassword) throws IOException {   
-        if("ntlm".equalsIgnoreCase(method.getProxyAuthState().getAuthScheme().getSchemeName())) {
-            // If Auth scheme is NTLM, set NT credentials with blank host and domain name
-            client.getState().setProxyCredentials(new AuthScope(proxyHost, proxyPort), 
-                            new NTCredentials(proxyUsername, proxyPassword,"",""));
-        } else {
-            // If Auth scheme is Basic/Digest, set regular Credentials
-            client.getState().setProxyCredentials(new AuthScope(proxyHost, proxyPort), 
-                    new UsernamePasswordCredentials(proxyUsername, proxyPassword));
-        }
-        
-        ProxyClient.ConnectResponse response = client.connect();
-        Socket socket = response.getSocket();
-        
-        if (socket == null) {
-            method = response.getConnectMethod();
-            throw new ProtocolException("Proxy Authentication failed. Socket not created: " 
-                    + method.getStatusLine());
-        }
-        return socket;
-    }
-    
-}
-

+ 0 - 296
src/main/java/com/notnoop/apns/internal/Utilities.java

@@ -1,296 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.apns.internal;
-
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.Socket;
-import java.security.GeneralSecurityException;
-import java.security.KeyStore;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Pattern;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManagerFactory;
-import com.notnoop.exceptions.InvalidSSLConfig;
-import com.notnoop.exceptions.NetworkIOException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public final class Utilities {
-    private static Logger logger = LoggerFactory.getLogger(Utilities.class);
-
-    public static final String SANDBOX_GATEWAY_HOST = "gateway.sandbox.push.apple.com";
-    public static final int SANDBOX_GATEWAY_PORT = 2195;
-
-    public static final String SANDBOX_FEEDBACK_HOST = "feedback.sandbox.push.apple.com";
-    public static final int SANDBOX_FEEDBACK_PORT = 2196;
-
-    public static final String PRODUCTION_GATEWAY_HOST = "gateway.push.apple.com";
-    public static final int PRODUCTION_GATEWAY_PORT = 2195;
-
-    public static final String PRODUCTION_FEEDBACK_HOST = "feedback.push.apple.com";
-    public static final int PRODUCTION_FEEDBACK_PORT = 2196;
-
-    public static final int MAX_PAYLOAD_LENGTH = 2048;
-
-    private Utilities() { throw new AssertionError("Uninstantiable class"); }
-
-    private static final Pattern pattern = Pattern.compile("[ -]");
-    public static byte[] decodeHex(final String deviceToken) {
-        final String hex = pattern.matcher(deviceToken).replaceAll("");
-
-        final byte[] bts = new byte[hex.length() / 2];
-        for (int i = 0; i < bts.length; i++) {
-            bts[i] = (byte) (charVal(hex.charAt(2 * i)) * 16 + charVal(hex.charAt(2 * i + 1)));
-        }
-        return bts;
-    }
-
-    private static int charVal(final char a) {
-        if ('0' <= a && a <= '9') {
-            return (a - '0');
-        } else if ('a' <= a && a <= 'f') {
-            return (a - 'a') + 10;
-        } else if ('A' <= a && a <= 'F') {
-            return (a - 'A') + 10;
-        } else {
-            throw new RuntimeException("Invalid hex character: " + a);
-        }
-    }
-
-    private static final char base[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
-
-    public static String encodeHex(final byte[] bytes) {
-        final char[] chars = new char[bytes.length * 2];
-
-        for (int i = 0; i < bytes.length; ++i) {
-            final int b = (bytes[i]) & 0xFF;
-            chars[2 * i] = base[b >>> 4];
-            chars[2 * i + 1] = base[b & 0xF];
-        }
-
-        return new String(chars);
-    }
-
-    public static byte[] toUTF8Bytes(final String s) {
-        try {
-            return s.getBytes("UTF-8");
-        } catch (final UnsupportedEncodingException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    public static byte[] marshall(final byte command, final byte[] deviceToken, final byte[] payload) {
-        final ByteArrayOutputStream boas = new ByteArrayOutputStream();
-        final DataOutputStream dos = new DataOutputStream(boas);
-
-        try {
-            dos.writeByte(command);
-            dos.writeShort(deviceToken.length);
-            dos.write(deviceToken);
-            dos.writeShort(payload.length);
-            dos.write(payload);
-            return boas.toByteArray();
-        } catch (final IOException e) {
-            throw new AssertionError();
-        }
-    }
-
-    public static byte[] marshallEnhanced(final byte command, final int identifier,
-            final int expiryTime, final byte[] deviceToken, final byte[] payload) {
-        final ByteArrayOutputStream boas = new ByteArrayOutputStream();
-        final DataOutputStream dos = new DataOutputStream(boas);
-
-        try {
-            dos.writeByte(command);
-            dos.writeInt(identifier);
-            dos.writeInt(expiryTime);
-            dos.writeShort(deviceToken.length);
-            dos.write(deviceToken);
-            dos.writeShort(payload.length);
-            dos.write(payload);
-            return boas.toByteArray();
-        } catch (final IOException e) {
-            throw new AssertionError();
-        }
-    }
-
-    public static Map<byte[], Integer> parseFeedbackStreamRaw(final InputStream in) {
-        final Map<byte[], Integer> result = new HashMap<byte[], Integer>();
-
-        final DataInputStream data = new DataInputStream(in);
-
-        while (true) {
-            try {
-                final int time = data.readInt();
-                final int dtLength = data.readUnsignedShort();
-                final byte[] deviceToken = new byte[dtLength];
-                data.readFully(deviceToken);
-
-                result.put(deviceToken, time);
-            } catch (final EOFException e) {
-                break;
-            } catch (final IOException e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        return result;
-    }
-
-    public static Map<String, Date> parseFeedbackStream(final InputStream in) {
-        final Map<String, Date> result = new HashMap<String, Date>();
-
-        final Map<byte[], Integer> raw = parseFeedbackStreamRaw(in);
-        for (final Map.Entry<byte[], Integer> entry : raw.entrySet()) {
-            final byte[] dtArray = entry.getKey();
-            final int time = entry.getValue(); // in seconds
-
-            final Date date = new Date(time * 1000L);    // in ms
-            final String dtString = encodeHex(dtArray);
-            result.put(dtString, date);
-        }
-
-        return result;
-    }
-
-    public static void close(final Closeable closeable) {
-        logger.debug("close {}", closeable);
-
-        try {
-            if (closeable != null) {
-                closeable.close();
-            }
-        } catch (final IOException e) {
-            logger.debug("error while closing resource", e);
-        }
-    }
-
-    public static void close(final Socket closeable) {
-        logger.debug("close {}", closeable);
-
-        try {
-            if (closeable != null) {
-                closeable.close();
-            }
-        } catch (final IOException e) {
-            logger.debug("error while closing socket", e);
-        }
-    }
-
-    public static void sleep(final int delay) {
-        try {
-            Thread.sleep(delay);
-        } catch (final InterruptedException e1) {
-            Thread.currentThread().interrupt();
-        }
-    }
-
-    public static byte[] copyOf(final byte[] bytes) {
-        final byte[] copy = new byte[bytes.length];
-        System.arraycopy(bytes, 0, copy, 0, bytes.length);
-        return copy;
-    }
-
-    public static byte[] copyOfRange(final byte[] original, final int from, final int to) {
-        final int newLength = to - from;
-        if (newLength < 0) {
-            throw new IllegalArgumentException(from + " > " + to);
-        }
-        final byte[] copy = new byte[newLength];
-        System.arraycopy(original, from, copy, 0,
-                Math.min(original.length - from, newLength));
-        return copy;
-    }
-
-    public static void wrapAndThrowAsRuntimeException(final Exception e) throws NetworkIOException {
-        if (e instanceof IOException) {
-            throw new NetworkIOException((IOException)e);
-        } else if (e instanceof NetworkIOException) {
-            throw (NetworkIOException)e;
-        } else if (e instanceof RuntimeException) {
-            throw (RuntimeException)e;
-        } else {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @SuppressWarnings({"PointlessArithmeticExpression", "PointlessBitwiseExpression"})
-    public static int parseBytes(final int b1, final int b2, final int b3, final int b4) {
-        return  ((b1 << 3 * 8) & 0xFF000000)
-              | ((b2 << 2 * 8) & 0x00FF0000)
-              | ((b3 << 1 * 8) & 0x0000FF00)
-              | ((b4 << 0 * 8) & 0x000000FF);
-    }
-
-    // @see http://stackoverflow.com/questions/119328/how-do-i-truncate-a-java-string-to-fit-in-a-given-number-of-bytes-once-utf-8-enc
-    public static String truncateWhenUTF8(final String s, final int maxBytes) {
-        int b = 0;
-        for (int i = 0; i < s.length(); i++) {
-            final char c = s.charAt(i);
-
-            // ranges from http://en.wikipedia.org/wiki/UTF-8
-            int skip = 0;
-            int more;
-            if (c <= 0x007f) {
-                more = 1;
-            }
-            else if (c <= 0x07FF) {
-                more = 2;
-            } else if (c <= 0xd7ff) {
-                more = 3;
-            } else if (c <= 0xDFFF) {
-                // surrogate area, consume next char as well
-                more = 4;
-                skip = 1;
-            } else {
-                more = 3;
-            }
-
-            if (b + more > maxBytes) {
-                return s.substring(0, i);
-            }
-            b += more;
-            i += skip;
-        }
-        return s;
-    }
-
-}

+ 0 - 61
src/main/java/com/notnoop/exceptions/ApnsDeliveryErrorException.java

@@ -1,61 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-/*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
-package com.notnoop.exceptions;
-
-import com.notnoop.apns.DeliveryError;
-
-/**
- *
- * @author kkirch
- */
-public class ApnsDeliveryErrorException extends ApnsException {
-
-    private final DeliveryError deliveryError;
-
-    public ApnsDeliveryErrorException(DeliveryError error) {
-        this.deliveryError = error;
-    }
-
-    @Override
-    public String getMessage() {
-        return "Failed to deliver notification with error code " + deliveryError.code();
-    }
-
-    public DeliveryError getDeliveryError() {
-        return deliveryError;
-    }
-    
-    
-}

+ 0 - 44
src/main/java/com/notnoop/exceptions/ApnsException.java

@@ -1,44 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.exceptions;
-
-/**
- * Base class for all the exceptions thrown in Apns Library
- */
-public abstract class ApnsException extends RuntimeException {
-    private static final long serialVersionUID = -4756693306121825229L;
-
-    public ApnsException()                      { super(); }
-    public ApnsException(String message)        { super(message); }
-    public ApnsException(Throwable cause)       { super(cause); }
-    public ApnsException(String m, Throwable c) { super(m, c); }
-
-}

+ 0 - 64
src/main/java/com/notnoop/exceptions/InvalidSSLConfig.java

@@ -1,64 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.exceptions;
-
-import java.io.IOException;
-import java.security.KeyManagementException;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.CertificateException;
-
-/**
- * Signals that the the provided SSL context settings (e.g.
- * keystore path, password, encryption type, etc) are invalid
- *
- * This Exception can be caused by any of the following:
- *
- * <ol>
- * <li>{@link KeyStoreException}</li>
- * <li>{@link NoSuchAlgorithmException}</li>
- * <li>{@link CertificateException}</li>
- * <li>{@link IOException}</li>
- * <li>{@link UnrecoverableKeyException}</li>
- * <li>{@link KeyManagementException}</li>
- * </ol>
- *
- */
-public class InvalidSSLConfig extends ApnsException {
-    private static final long serialVersionUID = -7283168775864517167L;
-
-    public InvalidSSLConfig()                      { super(); }
-    public InvalidSSLConfig(String message)        { super(message); }
-    public InvalidSSLConfig(Throwable cause)       { super(cause); }
-    public InvalidSSLConfig(String m, Throwable c) { super(m, c); }
-
-}

+ 0 - 69
src/main/java/com/notnoop/exceptions/NetworkIOException.java

@@ -1,69 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.exceptions;
-
-import java.io.IOException;
-
-/**
- * Thrown to indicate that that a network operation has failed:
- * (e.g. connectivity problems, domain cannot be found, network
- * dropped).
- */
-public class NetworkIOException extends ApnsException {
-    private static final long serialVersionUID = 3353516625486306533L;
-
-    private boolean resend;
-
-    public NetworkIOException()                      { super(); }
-    public NetworkIOException(String message)        { super(message); }
-    public NetworkIOException(IOException cause)       { super(cause); }
-    public NetworkIOException(String m, IOException c) { super(m, c); }
-    public NetworkIOException(IOException cause, boolean resend) {
-        super(cause);
-        this.resend = resend;
-    }
-
-    /**
-     * Identifies whether an exception was thrown during a resend of a
-     * message or not.  In this case a resend refers to whether the
-     * message is being resent from the buffer of messages internal.
-     * This would occur if we sent 5 messages quickly to APNS:
-     * 1,2,3,4,5 and the 3 message was rejected.  We would
-     * then need to resend 4 and 5.  If a network exception was
-     * triggered when doing this, then the resend flag will be
-     * {@code true}.
-     * @return {@code true} for an exception trigger during a resend, otherwise {@code false}.
-     */
-    public boolean isResend() {
-        return resend;
-    }
-
-}

+ 0 - 50
src/main/java/com/notnoop/exceptions/RuntimeIOException.java

@@ -1,50 +0,0 @@
-/*
- *  Copyright 2009, Mahmood Ali.
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
- *
- *    * Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    * Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following disclaimer
- *      in the documentation and/or other materials provided with the
- *      distribution.
- *    * Neither the name of Mahmood Ali. nor the names of its
- *      contributors may be used to endorse or promote products derived from
- *      this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.notnoop.exceptions;
-
-import java.io.IOException;
-
-/**
- * Signals that an I/O exception of some sort has occurred. This
- * class is the general class of exceptions produced by failed or
- * interrupted I/O operations.
- *
- * This is a RuntimeException, unlike the java.io.IOException
- */
-public class RuntimeIOException extends ApnsException {
-    private static final long serialVersionUID = 8665285084049041306L;
-
-    public RuntimeIOException()                      { super(); }
-    public RuntimeIOException(String message)        { super(message); }
-    public RuntimeIOException(IOException cause)       { super(cause); }
-    public RuntimeIOException(String m, IOException c) { super(m, c); }
-
-}

+ 3 - 1
src/main/resources/application.properties

@@ -1 +1,3 @@
-server.port=8085
+server.port=8085
+logging.level.root=debug
+logging.file=push.log

Some files were not shown because too many files changed in this diff