├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src └── main └── java ├── javapns ├── Push.java ├── communication │ ├── AppleServer.java │ ├── AppleServerBasicImpl.java │ ├── ConnectionToAppleServer.java │ ├── KeystoreManager.java │ ├── ProxyManager.java │ ├── ServerTrustingTrustManager.java │ ├── WrappedKeystore.java │ ├── exceptions │ │ ├── CommunicationException.java │ │ ├── InvalidCertificateChainException.java │ │ ├── InvalidKeystoreFormatException.java │ │ ├── InvalidKeystorePasswordException.java │ │ ├── InvalidKeystoreReferenceException.java │ │ ├── KeystoreException.java │ │ └── package.html │ └── package.html ├── devices │ ├── Device.java │ ├── DeviceFactory.java │ ├── Devices.java │ ├── exceptions │ │ ├── DuplicateDeviceException.java │ │ ├── InvalidDeviceTokenFormatException.java │ │ ├── NullDeviceTokenException.java │ │ ├── NullIdException.java │ │ ├── UnknownDeviceException.java │ │ └── package.html │ ├── implementations │ │ ├── basic │ │ │ ├── BasicDevice.java │ │ │ ├── BasicDeviceFactory.java │ │ │ └── package.html │ │ ├── jpa │ │ │ ├── injection.xml │ │ │ ├── package.html │ │ │ └── readme.txt │ │ └── package.html │ └── package.html ├── feedback │ ├── AppleFeedbackServer.java │ ├── AppleFeedbackServerBasicImpl.java │ ├── ConnectionToFeedbackServer.java │ ├── FeedbackServiceManager.java │ └── package.html ├── notification │ ├── AppleNotificationServer.java │ ├── AppleNotificationServerBasicImpl.java │ ├── ConnectionToNotificationServer.java │ ├── NewsstandNotificationPayload.java │ ├── Payload.java │ ├── PayloadPerDevice.java │ ├── PushNotificationBigPayload.java │ ├── PushNotificationManager.java │ ├── PushNotificationPayload.java │ ├── PushedNotification.java │ ├── PushedNotifications.java │ ├── ResponsePacket.java │ ├── ResponsePacketReader.java │ ├── exceptions │ │ ├── ErrorResponsePacketReceivedException.java │ │ ├── PayloadAlertAlreadyExistsException.java │ │ ├── PayloadIsEmptyException.java │ │ ├── PayloadMaxSizeExceededException.java │ │ ├── PayloadMaxSizeProbablyExceededException.java │ │ └── package.html │ ├── management │ │ ├── APNPayload.java │ │ ├── CalDAVPayload.java │ │ ├── CalendarSubscriptionPayload.java │ │ ├── EmailPayload.java │ │ ├── LDAPPayload.java │ │ ├── MobileConfigPayload.java │ │ ├── PasswordPolicyPayload.java │ │ ├── RemovalPasswordPayload.java │ │ ├── RestrictionsPayload.java │ │ ├── SCEPPayload.java │ │ ├── VPNPayload.java │ │ ├── WebClipPayload.java │ │ ├── WiFiPayload.java │ │ └── package.html │ ├── package.html │ └── transmission │ │ ├── NotificationProgressListener.java │ │ ├── NotificationThread.java │ │ ├── NotificationThreads.java │ │ ├── PushQueue.java │ │ └── package.html ├── overview.html ├── package.html └── test │ ├── FeedbackTest.java │ ├── NotificationTest.java │ ├── SpecificNotificationTests.java │ ├── TestFoundation.java │ └── package.html └── org └── json ├── CDL.java ├── Cookie.java ├── CookieList.java ├── HTTP.java ├── HTTPTokener.java ├── JSONArray.java ├── JSONException.java ├── JSONML.java ├── JSONNull.java ├── JSONObject.java ├── JSONRawValue.java ├── JSONString.java ├── JSONStringer.java ├── JSONTokener.java ├── JSONWriter.java ├── Test.java ├── XML.java ├── XMLTokener.java └── package.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | .project 4 | .classpath 5 | .settings 6 | target 7 | 8 | # Package Files # 9 | *.jar 10 | *.war 11 | *.ear 12 | 13 | .idea 14 | *.iml 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DEPRECATED 2 | ========== 3 | This library uses the legacy Apple API. Please, consider moving to a more efficient HTTP-2/based API, by using another library (e.g. https://github.com/CleverTap/apns-http2). 4 | 5 | I will stop mantaining this library, thanks everyone for your contributions. 6 | 7 | javapns-jdk16 8 | ============= 9 | 10 | Apple Push Notification Service Provider for Java 11 | 12 | Fork of JavaPNS to include Maven support - http://code.google.com/p/javapns/ 13 | 14 | Java1.6 compatible 15 | 16 | ## Updates 17 | 18 | Version 2.4.0 released! 19 | 20 | **2.4.0 Changes** 21 | * Added support for the following fields: 22 | * category 23 | * mutable-content (iOS>=10) 24 | * title, subtitle (iOS>=10), title-loc-key, title-loc-args and launch-image 25 | 26 | **2.3.1 Changes** 27 | * PushNotificationBigPayload ```complex``` and ```fromJson``` methods fixed 28 | * Fix to make trust store work on IBM JVM 29 | 30 | **2.3 Changes** 31 | * iOS>=8 bigger notification payload support (2KB) 32 | * iOS>=7 Silent push notifications support ("content-available":1) 33 | 34 | ## Installation through Central Maven Repository 35 | javapns-jdk16 is available on the Central Maven Repository. 36 | To use javapns-jdk16 in your project, please add the following dependency to your pom.xml file: 37 | ``` 38 | 39 | com.github.fernandospr 40 | javapns-jdk16 41 | 2.4.0 42 | 43 | ``` 44 | 45 | ## Usage 46 | 47 | You need to first construct the push notification payload. 48 | ``` 49 | PushNotificationBigPayload payload = ... // See Payload examples below 50 | ``` 51 | 52 | Then send it: 53 | ``` 54 | Push.payload(payload, p12File, password, isProduction, deviceTokens); 55 | ``` 56 | 57 | 58 | ## Payload examples 59 | 60 | Based on Apple docs 61 | 62 | #### Example 1 63 | To send the following payload: 64 | ``` 65 | { 66 | "aps" : { "alert" : "Message received from Bob" }, 67 | "acme2" : [ "bang", "whiz" ] 68 | } 69 | ``` 70 | You should code the following: 71 | ``` 72 | PushNotificationBigPayload payload = PushNotificationBigPayload.complex(); 73 | payload.addAlert("Message received from Bob"); 74 | payload.addCustomDictionary("acme2", Arrays.asList("bang", "whiz")); 75 | ``` 76 | 77 | #### Example 2 78 | To send the following payload: 79 | ``` 80 | { 81 | "aps" : { 82 | "alert" : { 83 | "title" : "Game Request", 84 | "body" : "Bob wants to play poker", 85 | "action-loc-key" : "PLAY" 86 | }, 87 | "badge" : 5 88 | }, 89 | "acme1" : "bar", 90 | "acme2" : [ "bang", "whiz" ] 91 | } 92 | ``` 93 | You should code the following: 94 | ``` 95 | PushNotificationBigPayload payload = PushNotificationBigPayload.complex(); 96 | payload.addCustomAlertTitle("Game Request"); 97 | payload.addCustomAlertBody("Bob wants to play poker"); 98 | payload.addCustomAlertActionLocKey("PLAY"); 99 | payload.addBadge(5); 100 | payload.addCustomDictionary("acme1", "bar"); 101 | payload.addCustomDictionary("acme2", Arrays.asList("bang", "whiz")); 102 | ``` 103 | 104 | #### Example 3 105 | To send the following payload: 106 | ``` 107 | { 108 | "aps" : { 109 | "alert" : "You got your emails.", 110 | "badge" : 9, 111 | "sound" : "bingbong.aiff" 112 | }, 113 | "acme1" : "bar", 114 | "acme2" : 42 115 | } 116 | ``` 117 | You should code the following: 118 | ``` 119 | PushNotificationBigPayload payload = PushNotificationBigPayload.complex(); 120 | payload.addCustomAlertBody("You got your emails."); 121 | payload.addBadge(9); 122 | payload.addSound("bingbong.aiff"); 123 | payload.addCustomDictionary("acme1", "bar"); 124 | payload.addCustomDictionary("acme2", 42); 125 | ``` 126 | 127 | #### Example 4 128 | To send the following payload: 129 | ``` 130 | { 131 | "aps" : { 132 | "alert" : { 133 | "loc-key" : "GAME_PLAY_REQUEST_FORMAT", 134 | "loc-args" : [ "Jenna", "Frank"] 135 | }, 136 | "sound" : "chime.aiff" 137 | }, 138 | "acme" : "foo" 139 | } 140 | ``` 141 | You should code the following: 142 | ``` 143 | PushNotificationBigPayload payload = PushNotificationBigPayload.complex(); 144 | payload.addCustomAlertLocKey("GAME_PLAY_REQUEST_FORMAT"); 145 | payload.addCustomAlertLocArgs(Arrays.asList("Jenna", "Frank")); 146 | payload.addSound("chime.aiff"); 147 | payload.addCustomDictionary("acme", "foo"); 148 | ``` 149 | 150 | 151 | #### Example 5 152 | To send the following payload: 153 | ``` 154 | { 155 | "aps" : { 156 | "content-available" : 1 157 | }, 158 | "acme" : "foo" 159 | } 160 | ``` 161 | You should code the following: 162 | ``` 163 | PushNotificationBigPayload payload = PushNotificationBigPayload.complex(); 164 | payload.setContentAvailable(true); 165 | payload.addCustomDictionary("acme", "foo"); 166 | ``` 167 | 168 | 169 | #### Example 6 170 | To send the following payload: 171 | ``` 172 | { 173 | "aps": { 174 | "mutable-content":1, 175 | "alert":{ 176 | "title": "Notification title", 177 | "subtitle": "Notification subtitle", 178 | "body": "Notification body" 179 | } 180 | }, 181 | "media-attachment": "https://url/to/content.mpg" 182 | } 183 | ``` 184 | You should code the following: 185 | ``` 186 | PushNotificationBigPayload payload = PushNotificationBigPayload.complex(); 187 | payload.setMutableContent(true); 188 | payload.addCustomAlertTitle("Notification title"); 189 | payload.addCustomAlertSubtitle("Notification subtitle"); 190 | payload.addCustomAlertBody("Notification body"); 191 | payload.addCustomDictionary("media-attachment", "https://url/to/content.mpg"); 192 | ``` 193 | 194 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.github.fernandospr 4 | javapns-jdk16 5 | jar 6 | 2.4.1-SNAPSHOT 7 | javapns-jdk16 8 | Library to send notifications using APNS 9 | https://github.com/fernandospr/javapns-jdk16 10 | 11 | 12 | GNU LGPL license v3.0 13 | http://opensource.org/licenses/lgpl-3.0.html 14 | repo 15 | 16 | 17 | 18 | 19 | 20 | org.sonatype.oss 21 | oss-parent 22 | 7 23 | 24 | 25 | 26 | scm:git:git@github.com:fernandospr/javapns-jdk16.git 27 | scm:git:git@github.com:fernandospr/javapns-jdk16.git 28 | scm:git:git@github.com:fernandospr/javapns-jdk16.git 29 | 30 | 31 | 32 | fernandospr 33 | Fernando Sproviero 34 | 35 | 36 | 37 | 38 | 39 | junit 40 | junit 41 | 3.8.1 42 | test 43 | 44 | 45 | 46 | 47 | log4j 48 | log4j 49 | 1.2.12 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | maven-assembly-plugin 58 | 59 | 60 | jar-with-dependencies 61 | 62 | 63 | 64 | 65 | package 66 | 67 | single 68 | 69 | 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-gpg-plugin 75 | 1.4 76 | 77 | 78 | sign-artifacts 79 | verify 80 | 81 | sign 82 | 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-source-plugin 89 | 2.2.1 90 | 91 | 92 | attach-sources 93 | 94 | jar 95 | 96 | 97 | 98 | 99 | 100 | org.apache.maven.plugins 101 | maven-javadoc-plugin 102 | 2.9 103 | 104 | 105 | attach-javadocs 106 | 107 | jar 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /src/main/java/javapns/communication/AppleServer.java: -------------------------------------------------------------------------------- 1 | package javapns.communication; 2 | 3 | import java.io.*; 4 | 5 | import javapns.communication.exceptions.*; 6 | 7 | /** 8 | * Common interface of all classes representing a connection to any Apple server. 9 | * Use AppleNotificationServer and AppleFeedbackServer interfaces for specific connections. 10 | * 11 | * @author Sylvain Pedneault 12 | */ 13 | public interface AppleServer { 14 | 15 | /** 16 | * Returns a stream to a keystore. 17 | * @return an InputStream 18 | */ 19 | public InputStream getKeystoreStream() throws InvalidKeystoreReferenceException; 20 | 21 | 22 | /** 23 | * Returns the keystore's password. 24 | * @return a password matching the keystore 25 | */ 26 | public String getKeystorePassword(); 27 | 28 | 29 | /** 30 | * Returns the format used to produce the keystore (typically PKCS12). 31 | * @return a valid keystore format identifier 32 | */ 33 | public String getKeystoreType(); 34 | 35 | 36 | /** 37 | * Get the proxy host address currently configured for this specific server. 38 | * A proxy might still be configured at the library or JVM levels. 39 | * Refer to {@link javapns.communication.ProxyManager} for more information. 40 | * @return a proxy host, or null if none is configured 41 | */ 42 | public String getProxyHost(); 43 | 44 | 45 | /** 46 | * Get the proxy port currently configured for this specific server. 47 | * A proxy might still be configured at the library or JVM levels. 48 | * Refer to {@link javapns.communication.ProxyManager} for more information. 49 | * @return a network port, or 0 if no proxy is configured 50 | */ 51 | public int getProxyPort(); 52 | 53 | 54 | /** 55 | * Configure a proxy to use for this specific server. 56 | * Use {@link javapns.communication.ProxyManager} to configure a proxy for the entire library instead. 57 | * @param proxyHost proxy host address 58 | * @param proxyPort proxy host port 59 | */ 60 | public void setProxy(String proxyHost, int proxyPort); 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/javapns/communication/AppleServerBasicImpl.java: -------------------------------------------------------------------------------- 1 | package javapns.communication; 2 | 3 | import java.io.*; 4 | 5 | import javapns.communication.exceptions.*; 6 | 7 | /** 8 | * A basic and abstract implementation of the AppleServer interface 9 | * intended to facilitate rapid deployment. 10 | * 11 | * @author Sylvain Pedneault 12 | */ 13 | public abstract class AppleServerBasicImpl implements AppleServer { 14 | 15 | private Object keystore; 16 | private final String password; 17 | private final String type; 18 | private String proxyHost; 19 | private int proxyPort; 20 | 21 | 22 | /** 23 | * Constructs a AppleServerBasicImpl object. 24 | * 25 | * @param keystore The keystore to use (can be a File, an InputStream, a String for a file path, or a byte[] array) 26 | * @param password The keystore's password 27 | * @param type The keystore type (typically PKCS12) 28 | * @throws KeystoreException thrown if an error occurs when loading the keystore 29 | */ 30 | public AppleServerBasicImpl(Object keystore, String password, String type) throws KeystoreException { 31 | KeystoreManager.validateKeystoreParameter(keystore); 32 | this.keystore = keystore; 33 | this.password = password; 34 | this.type = type; 35 | 36 | /* Make sure that the keystore reference is reusable. */ 37 | this.keystore = KeystoreManager.ensureReusableKeystore(this, this.keystore); 38 | } 39 | 40 | 41 | public InputStream getKeystoreStream() throws InvalidKeystoreReferenceException { 42 | return KeystoreManager.streamKeystore(keystore); 43 | } 44 | 45 | 46 | public String getKeystorePassword() { 47 | return password; 48 | } 49 | 50 | 51 | public String getKeystoreType() { 52 | return type; 53 | } 54 | 55 | 56 | public String getProxyHost() { 57 | return proxyHost; 58 | } 59 | 60 | 61 | public int getProxyPort() { 62 | return proxyPort; 63 | } 64 | 65 | 66 | public void setProxy(String proxyHost, int proxyPort) { 67 | this.proxyHost = proxyHost; 68 | this.proxyPort = proxyPort; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/javapns/communication/ConnectionToAppleServer.java: -------------------------------------------------------------------------------- 1 | package javapns.communication; 2 | 3 | import javapns.communication.exceptions.CommunicationException; 4 | import javapns.communication.exceptions.KeystoreException; 5 | import org.apache.log4j.Logger; 6 | 7 | import javax.net.ssl.*; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.OutputStream; 11 | import java.io.UnsupportedEncodingException; 12 | import java.net.Socket; 13 | import java.net.UnknownHostException; 14 | import java.security.KeyStore; 15 | 16 | /** 17 | *

Class representing an abstract connection to an Apple server

18 | * 19 | * Communication protocol differences between Notification and Feedback servers are 20 | * implemented in {@link javapns.notification.ConnectionToNotificationServer} and {@link javapns.feedback.ConnectionToFeedbackServer}. 21 | * 22 | * @author Sylvain Pedneault 23 | */ 24 | public abstract class ConnectionToAppleServer { 25 | 26 | protected static final Logger logger = Logger.getLogger(ConnectionToAppleServer.class); 27 | 28 | /* The algorithm used by KeyManagerFactory */ 29 | private static final String ALGORITHM = KeyManagerFactory.getDefaultAlgorithm(); 30 | 31 | /* The protocol used to create the SSLSocket */ 32 | private static final String PROTOCOL = "TLS"; 33 | 34 | /* PKCS12 */ 35 | public static final String KEYSTORE_TYPE_PKCS12 = "PKCS12"; 36 | /* JKS */ 37 | public static final String KEYSTORE_TYPE_JKS = "JKS"; 38 | 39 | private KeyStore keyStore; 40 | private SSLSocketFactory socketFactory; 41 | private AppleServer server; 42 | 43 | 44 | /** 45 | * Builds a connection to an Apple server. 46 | * 47 | * @param server connection details 48 | * @throws KeystoreException thrown if an error occurs when loading the keystore 49 | */ 50 | public ConnectionToAppleServer(AppleServer server) throws KeystoreException { 51 | this.server = server; 52 | this.keyStore = KeystoreManager.loadKeystore(server); 53 | } 54 | 55 | 56 | /** 57 | * Builds a connection to an Apple server. 58 | * 59 | * @param server connection details 60 | * @param keystore 61 | */ 62 | public ConnectionToAppleServer(AppleServer server, KeyStore keystore) { 63 | this.server = server; 64 | this.keyStore = keystore; 65 | } 66 | 67 | 68 | public AppleServer getServer() { 69 | return server; 70 | } 71 | 72 | 73 | public KeyStore getKeystore() { 74 | return keyStore; 75 | } 76 | 77 | 78 | public void setKeystore(KeyStore ks) { 79 | this.keyStore = ks; 80 | } 81 | 82 | 83 | /** 84 | * Generic SSLSocketFactory builder 85 | * 86 | * @param trustManagers 87 | * @return SSLSocketFactory 88 | * @throws KeystoreException 89 | */ 90 | protected SSLSocketFactory createSSLSocketFactoryWithTrustManagers(TrustManager[] trustManagers) throws KeystoreException { 91 | 92 | logger.debug("Creating SSLSocketFactory"); 93 | // Get a KeyManager and initialize it 94 | try { 95 | KeyStore keystore = getKeystore(); 96 | KeyManagerFactory kmf = KeyManagerFactory.getInstance(ALGORITHM); 97 | try { 98 | char[] password = KeystoreManager.getKeystorePasswordForSSL(server); 99 | kmf.init(keystore, password); 100 | } catch (Exception e) { 101 | e = KeystoreManager.wrapKeystoreException(e); 102 | throw e; 103 | } 104 | 105 | // Get the SSLContext to help create SSLSocketFactory 106 | SSLContext sslc = SSLContext.getInstance(PROTOCOL); 107 | sslc.init(kmf.getKeyManagers(), trustManagers, null); 108 | 109 | return sslc.getSocketFactory(); 110 | } catch (Exception e) { 111 | throw new KeystoreException("Keystore exception: " + e.getMessage(), e); 112 | } 113 | } 114 | 115 | 116 | public abstract String getServerHost(); 117 | 118 | 119 | public abstract int getServerPort(); 120 | 121 | 122 | /** 123 | * Return a SSLSocketFactory for creating sockets to communicate with Apple. 124 | * 125 | * @return SSLSocketFactory 126 | * @throws KeystoreException 127 | */ 128 | public SSLSocketFactory createSSLSocketFactory() throws KeystoreException { 129 | return createSSLSocketFactoryWithTrustManagers(new TrustManager[] { new ServerTrustingTrustManager() }); 130 | } 131 | 132 | 133 | public SSLSocketFactory getSSLSocketFactory() throws KeystoreException { 134 | if (socketFactory == null) socketFactory = createSSLSocketFactory(); 135 | return socketFactory; 136 | } 137 | 138 | 139 | /** 140 | * Create a SSLSocket which will be used to send data to Apple 141 | * @return the SSLSocket 142 | * @throws KeystoreException 143 | * @throws CommunicationException 144 | */ 145 | public SSLSocket getSSLSocket() throws KeystoreException, CommunicationException { 146 | SSLSocketFactory socketFactory = getSSLSocketFactory(); 147 | logger.debug("Creating SSLSocket to " + getServerHost() + ":" + getServerPort()); 148 | 149 | try { 150 | if (ProxyManager.isUsingProxy(server)) { 151 | return tunnelThroughProxy(socketFactory); 152 | } else { 153 | return (SSLSocket) socketFactory.createSocket(getServerHost(), getServerPort()); 154 | } 155 | } catch (Exception e) { 156 | throw new CommunicationException("Communication exception: " + e, e); 157 | } 158 | } 159 | 160 | 161 | private SSLSocket tunnelThroughProxy(SSLSocketFactory socketFactory) throws UnknownHostException, IOException { 162 | SSLSocket socket; 163 | 164 | // If a proxy was set, tunnel through the proxy to create the connection 165 | String tunnelHost = ProxyManager.getProxyHost(server); 166 | Integer tunnelPort = ProxyManager.getProxyPort(server); 167 | 168 | Socket tunnel = new Socket(tunnelHost, tunnelPort); 169 | doTunnelHandshake(tunnel, getServerHost(), getServerPort()); 170 | 171 | /* overlay the tunnel socket with SSL */ 172 | socket = (SSLSocket) socketFactory.createSocket(tunnel, getServerHost(), getServerPort(), true); 173 | 174 | /* register a callback for handshaking completion event */ 175 | socket.addHandshakeCompletedListener(new HandshakeCompletedListener() { 176 | public void handshakeCompleted(HandshakeCompletedEvent event) { 177 | logger.debug("Handshake finished!"); 178 | logger.debug("\t CipherSuite:" + event.getCipherSuite()); 179 | logger.debug("\t SessionId " + event.getSession()); 180 | logger.debug("\t PeerHost " + event.getSession().getPeerHost()); 181 | } 182 | }); 183 | 184 | return socket; 185 | } 186 | 187 | 188 | private void doTunnelHandshake(Socket tunnel, String host, int port) throws IOException { 189 | 190 | OutputStream out = tunnel.getOutputStream(); 191 | 192 | String msg = "CONNECT " + host + ":" + port + " HTTP/1.0\n" + "User-Agent: BoardPad Server" + "\r\n\r\n"; 193 | byte b[] = null; 194 | try { //We really do want ASCII7 -- the http protocol doesn't change with locale. 195 | b = msg.getBytes("ASCII7"); 196 | } catch (UnsupportedEncodingException ignored) { //If ASCII7 isn't there, something serious is wrong, but Paranoia Is Good (tm) 197 | b = msg.getBytes(); 198 | } 199 | out.write(b); 200 | out.flush(); 201 | 202 | // We need to store the reply so we can create a detailed error message to the user. 203 | byte reply[] = new byte[200]; 204 | int replyLen = 0; 205 | int newlinesSeen = 0; 206 | boolean headerDone = false; //Done on first newline 207 | 208 | InputStream in = tunnel.getInputStream(); 209 | 210 | while (newlinesSeen < 2) { 211 | int i = in.read(); 212 | if (i < 0) { 213 | throw new IOException("Unexpected EOF from proxy"); 214 | } 215 | if (i == '\n') { 216 | headerDone = true; 217 | ++newlinesSeen; 218 | } else if (i != '\r') { 219 | newlinesSeen = 0; 220 | if (!headerDone && replyLen < reply.length) { 221 | reply[replyLen++] = (byte) i; 222 | } 223 | } 224 | } 225 | 226 | /* 227 | * Converting the byte array to a string is slightly wasteful 228 | * in the case where the connection was successful, but it's 229 | * insignificant compared to the network overhead. 230 | */ 231 | String replyStr; 232 | try { 233 | replyStr = new String(reply, 0, replyLen, "ASCII7"); 234 | } catch (UnsupportedEncodingException ignored) { 235 | replyStr = new String(reply, 0, replyLen); 236 | } 237 | 238 | /* We check for Connection Established because our proxy returns HTTP/1.1 instead of 1.0 */ 239 | if (replyStr.toLowerCase().indexOf("200 connection established") == -1) { 240 | throw new IOException("Unable to tunnel through. Proxy returns \"" + replyStr + "\""); 241 | } 242 | 243 | /* tunneling Handshake was successful! */ 244 | } 245 | 246 | } 247 | -------------------------------------------------------------------------------- /src/main/java/javapns/communication/KeystoreManager.java: -------------------------------------------------------------------------------- 1 | package javapns.communication; 2 | 3 | import java.io.*; 4 | import java.security.*; 5 | import java.security.cert.*; 6 | import java.security.cert.Certificate; 7 | import java.util.*; 8 | 9 | import javapns.communication.exceptions.*; 10 | 11 | /** 12 | * Class responsible for dealing with keystores. 13 | * 14 | * @author Sylvain Pedneault 15 | */ 16 | public class KeystoreManager { 17 | 18 | private static final String REVIEW_MESSAGE = " Please review the procedure for generating a keystore for JavaPNS."; 19 | 20 | 21 | /** 22 | * Loads a keystore. 23 | * 24 | * @param server The server the keystore is intended for 25 | * @return A loaded keystore 26 | * @throws KeystoreException 27 | */ 28 | static KeyStore loadKeystore(AppleServer server) throws KeystoreException { 29 | return loadKeystore(server, server.getKeystoreStream()); 30 | } 31 | 32 | 33 | /** 34 | * Loads a keystore. 35 | * 36 | * @param server the server the keystore is intended for 37 | * @param keystore a keystore containing your private key and the certificate signed by Apple (File, InputStream, byte[], KeyStore or String for a file path) 38 | * @return a loaded keystore 39 | * @throws KeystoreException 40 | */ 41 | static KeyStore loadKeystore(AppleServer server, Object keystore) throws KeystoreException { 42 | return loadKeystore(server, keystore, false); 43 | } 44 | 45 | 46 | /** 47 | * Loads a keystore. 48 | * 49 | * @param server the server the keystore is intended for 50 | * @param keystore a keystore containing your private key and the certificate signed by Apple (File, InputStream, byte[], KeyStore or String for a file path) 51 | * @param verifyKeystore whether or not to perform basic verifications on the keystore to detect common mistakes. 52 | * @return a loaded keystore 53 | * @throws KeystoreException 54 | */ 55 | public static KeyStore loadKeystore(AppleServer server, Object keystore, boolean verifyKeystore) throws KeystoreException { 56 | if (keystore instanceof KeyStore) return (KeyStore) keystore; 57 | synchronized (server) { 58 | InputStream keystoreStream = streamKeystore(keystore); 59 | if (keystoreStream instanceof WrappedKeystore) return ((WrappedKeystore) keystoreStream).getKeystore(); 60 | KeyStore keyStore; 61 | try { 62 | keyStore = KeyStore.getInstance(server.getKeystoreType()); 63 | char[] password = KeystoreManager.getKeystorePasswordForSSL(server); 64 | keyStore.load(keystoreStream, password); 65 | } catch (Exception e) { 66 | throw wrapKeystoreException(e); 67 | } finally { 68 | try { 69 | keystoreStream.close(); 70 | } catch (Exception e) { 71 | } 72 | } 73 | return keyStore; 74 | } 75 | } 76 | 77 | 78 | /** 79 | * Make sure that the provided keystore will be reusable. 80 | * 81 | * @param server the server the keystore is intended for 82 | * @param keystore a keystore containing your private key and the certificate signed by Apple (File, InputStream, byte[], KeyStore or String for a file path) 83 | * @return a reusable keystore 84 | * @throws KeystoreException 85 | */ 86 | static Object ensureReusableKeystore(AppleServer server, Object keystore) throws KeystoreException { 87 | if (keystore instanceof InputStream) keystore = loadKeystore(server, keystore, false); 88 | return keystore; 89 | } 90 | 91 | 92 | /** 93 | * Perform basic tests on a keystore to detect common user mistakes. 94 | * If a problem is found, a KeystoreException is thrown. 95 | * If no problem is found, this method simply returns without exceptions. 96 | * 97 | * @param server the server the keystore is intended for 98 | * @param keystore a keystore containing your private key and the certificate signed by Apple (File, InputStream, byte[], KeyStore or String for a file path) 99 | * @throws KeystoreException 100 | */ 101 | public static void verifyKeystoreContent(AppleServer server, Object keystore) throws KeystoreException { 102 | KeyStore keystoreToValidate = null; 103 | if (keystore instanceof KeyStore) keystoreToValidate = (KeyStore) keystore; 104 | else keystoreToValidate = loadKeystore(server, keystore); 105 | verifyKeystoreContent(keystoreToValidate); 106 | } 107 | 108 | 109 | /** 110 | * Perform basic tests on a keystore to detect common user mistakes (experimental). 111 | * If a problem is found, a KeystoreException is thrown. 112 | * If no problem is found, this method simply returns without exceptions. 113 | * 114 | * @param keystore a keystore to verify 115 | * @throws KeystoreException thrown if a problem was detected 116 | */ 117 | public static void verifyKeystoreContent(KeyStore keystore) throws KeystoreException { 118 | try { 119 | int numberOfCertificates = 0; 120 | Enumeration aliases = keystore.aliases(); 121 | while (aliases.hasMoreElements()) { 122 | String alias = aliases.nextElement(); 123 | Certificate certificate = keystore.getCertificate(alias); 124 | if (certificate instanceof X509Certificate) { 125 | X509Certificate xcert = (X509Certificate) certificate; 126 | numberOfCertificates++; 127 | 128 | /* Check validity dates */ 129 | xcert.checkValidity(); 130 | 131 | /* Check issuer */ 132 | boolean issuerIsApple = xcert.getIssuerDN().toString().contains("Apple"); 133 | if (!issuerIsApple) throw new KeystoreException("Certificate was not issued by Apple." + REVIEW_MESSAGE); 134 | 135 | /* Check certificate key usage */ 136 | boolean[] keyUsage = xcert.getKeyUsage(); 137 | if (!keyUsage[0]) throw new KeystoreException("Certificate usage is incorrect." + REVIEW_MESSAGE); 138 | 139 | } 140 | } 141 | if (numberOfCertificates == 0) throw new KeystoreException("Keystore does not contain any valid certificate." + REVIEW_MESSAGE); 142 | if (numberOfCertificates > 1) throw new KeystoreException("Keystore contains too many certificates." + REVIEW_MESSAGE); 143 | 144 | } catch (KeystoreException e) { 145 | throw e; 146 | } catch (CertificateExpiredException e) { 147 | throw new KeystoreException("Certificate is expired. A new one must be issued.", e); 148 | } catch (CertificateNotYetValidException e) { 149 | throw new KeystoreException("Certificate is not yet valid. Wait until the validity period is reached or issue a new certificate.", e); 150 | } catch (Exception e) { 151 | /* We ignore any other exception, as we do not want to interrupt the process because of an error we did not expect. */ 152 | } 153 | } 154 | 155 | 156 | static char[] getKeystorePasswordForSSL(AppleServer server) { 157 | String password = server.getKeystorePassword(); 158 | if (password == null) password = ""; 159 | // if (password != null && password.length() == 0) password = null; 160 | char[] passchars = password != null ? password.toCharArray() : null; 161 | return passchars; 162 | } 163 | 164 | 165 | static KeystoreException wrapKeystoreException(Exception e) { 166 | if (e != null) { 167 | String msg = e.toString(); 168 | if (msg.contains("javax.crypto.BadPaddingException")) { 169 | return new InvalidKeystorePasswordException(); 170 | } 171 | if (msg.contains("DerInputStream.getLength(): lengthTag=127, too big")) { 172 | return new InvalidKeystoreFormatException(); 173 | } 174 | if (msg.contains("java.lang.ArithmeticException: / by zero") || msg.contains("java.security.UnrecoverableKeyException: Get Key failed: / by zero")) { 175 | return new InvalidKeystorePasswordException("Blank passwords not supported (#38). You must create your keystore with a non-empty password."); 176 | } 177 | } 178 | return new KeystoreException("Keystore exception: " + e.getMessage(), e); 179 | } 180 | 181 | 182 | /** 183 | * Given an object representing a keystore, returns an actual stream for that keystore. 184 | * Allows you to provide an actual keystore as an InputStream or a byte[] array, 185 | * or a reference to a keystore file as a File object or a String path. 186 | * 187 | * @param keystore a keystore containing your private key and the certificate signed by Apple (File, InputStream, byte[], KeyStore or String for a file path) 188 | * @return A stream to the keystore. 189 | * @throws FileNotFoundException 190 | */ 191 | static InputStream streamKeystore(Object keystore) throws InvalidKeystoreReferenceException { 192 | validateKeystoreParameter(keystore); 193 | try { 194 | if (keystore instanceof InputStream) return (InputStream) keystore; 195 | else if (keystore instanceof KeyStore) return new WrappedKeystore((KeyStore) keystore); 196 | else if (keystore instanceof File) return new BufferedInputStream(new FileInputStream((File) keystore)); 197 | else if (keystore instanceof String) return new BufferedInputStream(new FileInputStream((String) keystore)); 198 | else if (keystore instanceof byte[]) return new ByteArrayInputStream((byte[]) keystore); 199 | else return null; // we should not get here since validateKeystore ensures that the reference is valid 200 | } catch (Exception e) { 201 | throw new InvalidKeystoreReferenceException("Invalid keystore reference: " + e.getMessage()); 202 | } 203 | } 204 | 205 | 206 | /** 207 | * Ensures that a keystore parameter is actually supported by the KeystoreManager. 208 | * 209 | * @param keystore a keystore containing your private key and the certificate signed by Apple (File, InputStream, byte[], KeyStore or String for a file path) 210 | * @throws InvalidKeystoreReferenceException thrown if the provided keystore parameter is not supported 211 | */ 212 | public static void validateKeystoreParameter(Object keystore) throws InvalidKeystoreReferenceException { 213 | if (keystore == null) throw new InvalidKeystoreReferenceException((Object) null); 214 | if (keystore instanceof KeyStore) return; 215 | if (keystore instanceof InputStream) return; 216 | if (keystore instanceof String) keystore = new File((String) keystore); 217 | if (keystore instanceof File) { 218 | File file = (File) keystore; 219 | if (!file.exists()) throw new InvalidKeystoreReferenceException("Invalid keystore reference. File does not exist: " + file.getAbsolutePath()); 220 | if (!file.isFile()) throw new InvalidKeystoreReferenceException("Invalid keystore reference. Path does not refer to a valid file: " + file.getAbsolutePath()); 221 | if (file.length() <= 0) throw new InvalidKeystoreReferenceException("Invalid keystore reference. File is empty: " + file.getAbsolutePath()); 222 | return; 223 | } 224 | if (keystore instanceof byte[]) { 225 | byte[] bytes = (byte[]) keystore; 226 | if (bytes.length == 0) throw new InvalidKeystoreReferenceException("Invalid keystore reference. Byte array is empty"); 227 | return; 228 | } 229 | throw new InvalidKeystoreReferenceException(keystore); 230 | } 231 | 232 | } 233 | -------------------------------------------------------------------------------- /src/main/java/javapns/communication/ProxyManager.java: -------------------------------------------------------------------------------- 1 | package javapns.communication; 2 | 3 | /** 4 | * Main class for dealing with proxies. 5 | * 6 | * @author Sylvain Pedneault 7 | */ 8 | public class ProxyManager { 9 | 10 | private static final String LOCAL_PROXY_HOST_PROPERTY = "javapns.communication.proxyHost"; 11 | private static final String LOCAL_PROXY_PORT_PROPERTY = "javapns.communication.proxyPort"; 12 | 13 | private static final String JVM_PROXY_HOST_PROPERTY = "https.proxyHost"; 14 | private static final String JVM_PROXY_PORT_PROPERTY = "https.proxyPort"; 15 | 16 | 17 | private ProxyManager() { 18 | } 19 | 20 | 21 | /** 22 | * Configure a proxy to use for HTTPS connections created by JavaPNS. 23 | * 24 | * @param host the proxyHost 25 | * @param port the proxyPort 26 | */ 27 | public static void setProxy(String host, String port) { 28 | System.setProperty(LOCAL_PROXY_HOST_PROPERTY, host); 29 | System.setProperty(LOCAL_PROXY_PORT_PROPERTY, port); 30 | } 31 | 32 | 33 | /** 34 | * Configure a proxy to use for HTTPS connections created anywhere in the JVM (not recommended). 35 | * 36 | * @param host the proxyHost 37 | * @param port the proxyPort 38 | */ 39 | public static void setJVMProxy(String host, String port) { 40 | System.setProperty(JVM_PROXY_HOST_PROPERTY, host); 41 | System.setProperty(JVM_PROXY_PORT_PROPERTY, port); 42 | } 43 | 44 | 45 | /** 46 | * Get the proxy host address currently configured. 47 | * This method checks if a server-specific proxy has been configured, 48 | * then checks if a proxy has been configured for the entire library, 49 | * and finally checks if a JVM-wide proxy setting is available for HTTPS. 50 | * @param server a specific server to check for proxy settings (may be null) 51 | * @return a proxy host, or null if none is configured 52 | */ 53 | public static String getProxyHost(AppleServer server) { 54 | String host = server != null ? server.getProxyHost() : null; 55 | if (host != null && host.length() > 0) { 56 | return host; 57 | } else { 58 | host = System.getProperty(LOCAL_PROXY_HOST_PROPERTY); 59 | if (host != null && host.length() > 0) { 60 | return host; 61 | } else { 62 | host = System.getProperty(JVM_PROXY_HOST_PROPERTY); 63 | if (host != null && host.length() > 0) { 64 | return host; 65 | } else { 66 | return null; 67 | } 68 | } 69 | } 70 | } 71 | 72 | 73 | /** 74 | * Get the proxy port currently configured. 75 | * This method first locates a proxy host setting, then returns the proxy port from the same location. 76 | * @param server a specific server to check for proxy settings (may be null) 77 | * @return a network port, or 0 if no proxy is configured 78 | */ 79 | public static int getProxyPort(AppleServer server) { 80 | String host = server != null ? server.getProxyHost() : null; 81 | if (host != null && host.length() > 0) { 82 | return server.getProxyPort(); 83 | } else { 84 | host = System.getProperty(LOCAL_PROXY_HOST_PROPERTY); 85 | if (host != null && host.length() > 0) { 86 | return Integer.parseInt(System.getProperty(LOCAL_PROXY_PORT_PROPERTY)); 87 | } else { 88 | host = System.getProperty(JVM_PROXY_HOST_PROPERTY); 89 | if (host != null && host.length() > 0) { 90 | return Integer.parseInt(System.getProperty(JVM_PROXY_PORT_PROPERTY)); 91 | } else { 92 | return 0; 93 | } 94 | } 95 | } 96 | } 97 | 98 | 99 | /** 100 | * Determine if a proxy is currently configured. 101 | * @param server a specific server to check for proxy settings (may be null) 102 | * @return true if a proxy is set, false otherwise 103 | */ 104 | public static boolean isUsingProxy(AppleServer server) { 105 | String proxyHost = getProxyHost(server); 106 | boolean proxyConfigured = proxyHost != null && proxyHost.length() > 0; 107 | return proxyConfigured; 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/javapns/communication/ServerTrustingTrustManager.java: -------------------------------------------------------------------------------- 1 | package javapns.communication; 2 | 3 | import java.security.cert.*; 4 | 5 | import javax.net.ssl.*; 6 | 7 | /** 8 | * A trust manager that automatically trusts all servers. 9 | * Used to avoid having handshake errors with Apple's sandbox servers. 10 | * 11 | * @author Sylvain Pedneault 12 | */ 13 | class ServerTrustingTrustManager implements X509TrustManager { 14 | 15 | public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 16 | throw new CertificateException("Client is not trusted."); 17 | } 18 | 19 | 20 | public void checkServerTrusted(X509Certificate[] chain, String authType) { 21 | // trust all servers 22 | } 23 | 24 | 25 | public X509Certificate[] getAcceptedIssuers() { 26 | return null;//new X509Certificate[0]; 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/javapns/communication/WrappedKeystore.java: -------------------------------------------------------------------------------- 1 | package javapns.communication; 2 | 3 | import java.io.*; 4 | import java.security.*; 5 | 6 | /** 7 | * Special wrapper for a KeyStore. 8 | * 9 | * @author Sylvain Pedneault 10 | */ 11 | class WrappedKeystore extends InputStream { 12 | 13 | private final KeyStore keystore; 14 | 15 | 16 | public WrappedKeystore(KeyStore keystore) { 17 | this.keystore = keystore; 18 | } 19 | 20 | 21 | @Override 22 | public int read() throws IOException { 23 | return 0; 24 | } 25 | 26 | 27 | public KeyStore getKeystore() { 28 | return keystore; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/javapns/communication/exceptions/CommunicationException.java: -------------------------------------------------------------------------------- 1 | package javapns.communication.exceptions; 2 | 3 | public class CommunicationException extends Exception { 4 | 5 | private static final long serialVersionUID = 1L; 6 | 7 | 8 | public CommunicationException(String message, Exception cause) { 9 | super(message, cause); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/javapns/communication/exceptions/InvalidCertificateChainException.java: -------------------------------------------------------------------------------- 1 | package javapns.communication.exceptions; 2 | 3 | /** 4 | * Thrown when we try to contact Apple with an invalid keystore or certificate chain. 5 | * @author Sylvain Pedneault 6 | * 7 | */ 8 | @SuppressWarnings("serial") 9 | public class InvalidCertificateChainException extends KeystoreException { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | public InvalidCertificateChainException() { 15 | super("Invalid certificate chain! Verify that the keystore you provided was produced according to specs..."); 16 | } 17 | 18 | 19 | /** 20 | * Constructor with custom message 21 | * @param message 22 | */ 23 | public InvalidCertificateChainException(String message) { 24 | super("Invalid certificate chain (" + message + ")! Verify that the keystore you provided was produced according to specs..."); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/javapns/communication/exceptions/InvalidKeystoreFormatException.java: -------------------------------------------------------------------------------- 1 | package javapns.communication.exceptions; 2 | 3 | /** 4 | * Thrown when we try to contact Apple with an invalid keystore format. 5 | * @author Sylvain Pedneault 6 | * 7 | */ 8 | @SuppressWarnings("serial") 9 | public class InvalidKeystoreFormatException extends KeystoreException { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | public InvalidKeystoreFormatException() { 15 | super("Invalid keystore format! Make sure it is PKCS12..."); 16 | } 17 | 18 | 19 | /** 20 | * Constructor with custom message 21 | * @param message 22 | */ 23 | public InvalidKeystoreFormatException(String message) { 24 | super(message); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/javapns/communication/exceptions/InvalidKeystorePasswordException.java: -------------------------------------------------------------------------------- 1 | package javapns.communication.exceptions; 2 | 3 | /** 4 | * Thrown when we try to contact Apple with an invalid password for the keystore. 5 | * @author Sylvain Pedneault 6 | * 7 | */ 8 | @SuppressWarnings("serial") 9 | public class InvalidKeystorePasswordException extends KeystoreException { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | public InvalidKeystorePasswordException() { 15 | super("Invalid keystore password! Verify settings for connecting to Apple..."); 16 | } 17 | 18 | 19 | /** 20 | * Constructor with custom message 21 | * @param message 22 | */ 23 | public InvalidKeystorePasswordException(String message) { 24 | super(message); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/javapns/communication/exceptions/InvalidKeystoreReferenceException.java: -------------------------------------------------------------------------------- 1 | package javapns.communication.exceptions; 2 | 3 | /** 4 | * Thrown when we try to contact Apple with an invalid keystore format. 5 | * @author Sylvain Pedneault 6 | * 7 | */ 8 | @SuppressWarnings("serial") 9 | public class InvalidKeystoreReferenceException extends KeystoreException { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | public InvalidKeystoreReferenceException() { 15 | super("Invalid keystore parameter. Must be InputStream, File, String (as a file path), or byte[]."); 16 | } 17 | 18 | 19 | /** 20 | * Constructor with custom message 21 | * @param keystore 22 | */ 23 | public InvalidKeystoreReferenceException(Object keystore) { 24 | super("Invalid keystore parameter (" + keystore + "). Must be InputStream, File, String (as a file path), or byte[]."); 25 | } 26 | 27 | 28 | /** 29 | * Constructor with custom message 30 | * @param message 31 | */ 32 | public InvalidKeystoreReferenceException(String message) { 33 | super(message); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/javapns/communication/exceptions/KeystoreException.java: -------------------------------------------------------------------------------- 1 | package javapns.communication.exceptions; 2 | 3 | /** 4 | * Thrown when we try to contact Apple with an invalid keystore format. 5 | * @author Sylvain Pedneault 6 | * 7 | */ 8 | @SuppressWarnings("serial") 9 | public class KeystoreException extends Exception { 10 | 11 | /** 12 | * Constructor with custom message 13 | * @param message 14 | */ 15 | public KeystoreException(String message) { 16 | super(message); 17 | } 18 | 19 | /** 20 | * Constructor with custom message 21 | * @param message 22 | */ 23 | public KeystoreException(String message, Exception cause) { 24 | super(message, cause); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/javapns/communication/exceptions/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | Communication-related exceptions thrown by the javapns library. 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/javapns/communication/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | Classes for communicating with Apple servers. 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/javapns/devices/Device.java: -------------------------------------------------------------------------------- 1 | package javapns.devices; 2 | 3 | import java.sql.*; 4 | 5 | /** 6 | * This is the common interface for all Devices. 7 | * It allows the DeviceFactory to support multiple 8 | * implementations of Device (in-memory, JPA-backed, etc.) 9 | * 10 | * @author Sylvain Pedneault 11 | */ 12 | public interface Device { 13 | 14 | /** 15 | * An id representing a particular device. 16 | * 17 | * Note that this is a local reference to the device, 18 | * which is not related to the actual device UUID or 19 | * other device-specific identification. Most of the 20 | * time, this deviceId should be the same as the token. 21 | * 22 | * @return the device id 23 | */ 24 | public String getDeviceId(); 25 | 26 | 27 | /** 28 | * A device token. 29 | * 30 | * @return the device token 31 | */ 32 | public String getToken(); 33 | 34 | 35 | /** 36 | * 37 | * @return the last register 38 | */ 39 | public Timestamp getLastRegister(); 40 | 41 | 42 | /** 43 | * An id representing a particular device. 44 | * 45 | * Note that this is a local reference to the device, 46 | * which is not related to the actual device UUID or 47 | * other device-specific identification. Most of the 48 | * time, this deviceId should be the same as the token. 49 | * 50 | * @param id the device id 51 | */ 52 | public void setDeviceId(String id); 53 | 54 | 55 | /** 56 | * Set the device token 57 | * @param token 58 | */ 59 | public void setToken(String token); 60 | 61 | 62 | /** 63 | * 64 | * @param lastRegister the last register 65 | */ 66 | public void setLastRegister(Timestamp lastRegister); 67 | 68 | } -------------------------------------------------------------------------------- /src/main/java/javapns/devices/DeviceFactory.java: -------------------------------------------------------------------------------- 1 | package javapns.devices; 2 | 3 | import javapns.devices.exceptions.*; 4 | 5 | /** 6 | * This is the common interface for all DeviceFactories. 7 | * It allows the PushNotificationManager to support multiple 8 | * implementations of DeviceFactory (in-memory, JPA-backed, etc.) 9 | * 10 | * @author Sylvain Pedneault 11 | * @deprecated Phasing out DeviceFactory because it has become irrelevant in the new library architecture 12 | */ 13 | @Deprecated 14 | public interface DeviceFactory { 15 | 16 | /** 17 | * Add a device to the map 18 | * @param id The local device id 19 | * @param token The device token 20 | * @return The device created 21 | * @throws DuplicateDeviceException 22 | * @throws NullIdException 23 | * @throws NullDeviceTokenException 24 | */ 25 | public Device addDevice(String id, String token) throws DuplicateDeviceException, NullIdException, NullDeviceTokenException, Exception; 26 | 27 | 28 | /** 29 | * Get a device according to his id 30 | * @param id The local device id 31 | * @return The device 32 | * @throws UnknownDeviceException 33 | * @throws NullIdException 34 | */ 35 | public Device getDevice(String id) throws UnknownDeviceException, NullIdException; 36 | 37 | 38 | /** 39 | * Remove a device 40 | * @param id The local device id 41 | * @throws UnknownDeviceException 42 | * @throws NullIdException 43 | */ 44 | public void removeDevice(String id) throws UnknownDeviceException, NullIdException; 45 | 46 | } -------------------------------------------------------------------------------- /src/main/java/javapns/devices/Devices.java: -------------------------------------------------------------------------------- 1 | package javapns.devices; 2 | 3 | import java.util.*; 4 | 5 | import javapns.devices.implementations.basic.*; 6 | import javapns.notification.*; 7 | 8 | public class Devices { 9 | 10 | @SuppressWarnings("unchecked") 11 | public static List asDevices(Object rawList) { 12 | List list = new Vector(); 13 | if (rawList == null) return list; 14 | 15 | if (rawList instanceof List) { 16 | List devices = (List) rawList; 17 | if (devices == null || devices.size() == 0) return list; 18 | Object firstDevice = devices.get(0); 19 | if (firstDevice instanceof Device) return devices; 20 | else if (firstDevice instanceof String) { 21 | for (Object token : devices) { 22 | BasicDevice device = new BasicDevice(); 23 | device.setToken((String) token); 24 | list.add(device); 25 | } 26 | } 27 | } else if (rawList instanceof String[]) { 28 | String[] tokens = (String[]) rawList; 29 | for (String token : tokens) { 30 | BasicDevice device = new BasicDevice(); 31 | device.setToken(token); 32 | list.add(device); 33 | } 34 | } else if (rawList instanceof Device[]) { 35 | Device[] dvs = (Device[]) rawList; 36 | return Arrays.asList(dvs); 37 | } else if (rawList instanceof String) { 38 | BasicDevice device = new BasicDevice(); 39 | device.setToken((String) rawList); 40 | list.add(device); 41 | } else if (rawList instanceof Device) { 42 | list.add((Device) rawList); 43 | } else throw new IllegalArgumentException("Device list type not supported. Supported types are: String[], List, Device[], List, String and Device"); 44 | return list; 45 | } 46 | 47 | 48 | @SuppressWarnings("unchecked") 49 | public static List asPayloadsPerDevices(Object rawList) { 50 | List list = new Vector(); 51 | if (rawList == null) return list; 52 | if (rawList instanceof List) { 53 | List devices = (List) rawList; 54 | if (devices == null || devices.size() == 0) return list; 55 | return devices; 56 | } else if (rawList instanceof PayloadPerDevice[]) { 57 | PayloadPerDevice[] dvs = (PayloadPerDevice[]) rawList; 58 | return Arrays.asList(dvs); 59 | } else if (rawList instanceof PayloadPerDevice) { 60 | list.add((PayloadPerDevice) rawList); 61 | } else throw new IllegalArgumentException("PayloadPerDevice list type not supported. Supported types are: PayloadPerDevice[], List and PayloadPerDevice"); 62 | return list; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/javapns/devices/exceptions/DuplicateDeviceException.java: -------------------------------------------------------------------------------- 1 | package javapns.devices.exceptions; 2 | 3 | /** 4 | * Thrown when a Device already exist and we try to add it a second time 5 | * @author Maxime Peron 6 | */ 7 | @SuppressWarnings("serial") 8 | public class DuplicateDeviceException extends Exception{ 9 | 10 | /* Custom message for this exception */ 11 | private String message; 12 | 13 | /** 14 | * Constructor 15 | */ 16 | public DuplicateDeviceException(){ 17 | this.message = "Client already exists"; 18 | } 19 | 20 | /** 21 | * Constructor with custom message 22 | * @param message 23 | */ 24 | public DuplicateDeviceException(String message){ 25 | this.message = message; 26 | } 27 | 28 | /** 29 | * String representation 30 | */ 31 | public String toString(){ 32 | return this.message; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/javapns/devices/exceptions/InvalidDeviceTokenFormatException.java: -------------------------------------------------------------------------------- 1 | package javapns.devices.exceptions; 2 | 3 | /** 4 | * Thrown when a device token cannot be parsed (invalid format). 5 | * 6 | * @author Sylvain Pedneault 7 | */ 8 | @SuppressWarnings("serial") 9 | public class InvalidDeviceTokenFormatException extends Exception { 10 | 11 | public InvalidDeviceTokenFormatException(String message) { 12 | super(message); 13 | } 14 | 15 | 16 | public InvalidDeviceTokenFormatException(String token, String problem) { 17 | super(String.format("Device token cannot be parsed, most likely because it contains invalid hexadecimal characters: %s in %s", problem, token)); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/javapns/devices/exceptions/NullDeviceTokenException.java: -------------------------------------------------------------------------------- 1 | package javapns.devices.exceptions; 2 | 3 | /** 4 | * Thrown when the given token is null 5 | * @author Maxime Peron 6 | * 7 | */ 8 | @SuppressWarnings("serial") 9 | public class NullDeviceTokenException extends Exception{ 10 | 11 | /* Custom message for this exception */ 12 | private String message; 13 | 14 | /** 15 | * Constructor 16 | */ 17 | public NullDeviceTokenException(){ 18 | this.message = "Client already exists"; 19 | } 20 | 21 | /** 22 | * Constructor with custom message 23 | * @param message 24 | */ 25 | public NullDeviceTokenException(String message){ 26 | this.message = message; 27 | } 28 | 29 | /** 30 | * String representation 31 | */ 32 | public String toString(){ 33 | return this.message; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/javapns/devices/exceptions/NullIdException.java: -------------------------------------------------------------------------------- 1 | package javapns.devices.exceptions; 2 | 3 | /** 4 | * Thrown when the given id is null 5 | * @author Maxime Peron 6 | */ 7 | @SuppressWarnings("serial") 8 | public class NullIdException extends Exception{ 9 | 10 | /* Custom message for this exception */ 11 | private String message; 12 | 13 | /** 14 | * Constructor 15 | */ 16 | public NullIdException(){ 17 | this.message = "Client already exists"; 18 | } 19 | 20 | /** 21 | * Constructor with custom message 22 | * @param message 23 | */ 24 | public NullIdException(String message){ 25 | this.message = message; 26 | } 27 | 28 | /** 29 | * String representation 30 | */ 31 | public String toString(){ 32 | return this.message; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/javapns/devices/exceptions/UnknownDeviceException.java: -------------------------------------------------------------------------------- 1 | package javapns.devices.exceptions; 2 | 3 | /** 4 | * Thrown when we try to retrieve a device that doesn't exist 5 | * @author Maxime Peron 6 | * 7 | */ 8 | @SuppressWarnings("serial") 9 | public class UnknownDeviceException extends Exception{ 10 | 11 | /* Custom message for this exception */ 12 | private String message; 13 | 14 | /** 15 | * Constructor 16 | */ 17 | public UnknownDeviceException(){ 18 | this.message = "Unknown client"; 19 | } 20 | 21 | /** 22 | * Constructor with custom message 23 | * @param message 24 | */ 25 | public UnknownDeviceException(String message){ 26 | this.message = message; 27 | } 28 | 29 | /** 30 | * String representation 31 | */ 32 | public String toString(){ 33 | return this.message; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/javapns/devices/exceptions/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | Device-related exceptions thrown by the javapns library. 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/javapns/devices/implementations/basic/BasicDevice.java: -------------------------------------------------------------------------------- 1 | package javapns.devices.implementations.basic; 2 | 3 | import java.sql.*; 4 | 5 | import javapns.devices.*; 6 | import javapns.devices.exceptions.*; 7 | 8 | /** 9 | * This class is used to represent a Device (iPhone) 10 | * @author Maxime Peron 11 | * 12 | */ 13 | public class BasicDevice implements Device { 14 | 15 | /* 16 | * An id representing a particular device. 17 | * 18 | * Note that this is a local reference to the device, 19 | * which is not related to the actual device UUID or 20 | * other device-specific identification. Most of the 21 | * time, this deviceId should be the same as the token. 22 | */ 23 | private String deviceId; 24 | 25 | /* The device token given by Apple Server, hexadecimal form, 64bits length */ 26 | private String token; 27 | 28 | /* The last time a device registered */ 29 | private Timestamp lastRegister; 30 | 31 | 32 | /** 33 | * Default constructor. 34 | * @param token The device token 35 | */ 36 | public BasicDevice(String token) throws InvalidDeviceTokenFormatException { 37 | this(token, true); 38 | } 39 | 40 | 41 | public BasicDevice(String token, boolean validate) throws InvalidDeviceTokenFormatException { 42 | super(); 43 | this.deviceId = token; 44 | this.token = token; 45 | try { 46 | this.lastRegister = new Timestamp(System.currentTimeMillis()); 47 | } catch (Exception e) { 48 | } 49 | if (validate) validateTokenFormat(token); 50 | } 51 | 52 | 53 | public BasicDevice() { 54 | } 55 | 56 | 57 | public void validateTokenFormat() throws InvalidDeviceTokenFormatException { 58 | validateTokenFormat(token); 59 | } 60 | 61 | 62 | public static void validateTokenFormat(String token) throws InvalidDeviceTokenFormatException { 63 | if (token == null) { 64 | throw new InvalidDeviceTokenFormatException("Device Token is null, and not the required 64 bytes..."); 65 | } 66 | if (token.getBytes().length != 64) { 67 | throw new InvalidDeviceTokenFormatException("Device Token has a length of [" + token.getBytes().length + "] and not the required 64 bytes!"); 68 | } 69 | } 70 | 71 | 72 | /** 73 | * Constructor 74 | * @param id The device id 75 | * @param token The device token 76 | */ 77 | public BasicDevice(String id, String token, Timestamp register) throws InvalidDeviceTokenFormatException { 78 | super(); 79 | this.deviceId = id; 80 | this.token = token; 81 | this.lastRegister = register; 82 | 83 | validateTokenFormat(token); 84 | 85 | } 86 | 87 | 88 | /** 89 | * Getter 90 | * @return the device id 91 | */ 92 | public String getDeviceId() { 93 | return deviceId; 94 | } 95 | 96 | 97 | /** 98 | * Getter 99 | * @return the device token 100 | */ 101 | public String getToken() { 102 | return token; 103 | } 104 | 105 | 106 | /** 107 | * Getter 108 | * @return the last register 109 | */ 110 | public Timestamp getLastRegister() { 111 | return lastRegister; 112 | } 113 | 114 | 115 | /** 116 | * Setter 117 | * @param id the device id 118 | */ 119 | public void setDeviceId(String id) { 120 | this.deviceId = id; 121 | } 122 | 123 | 124 | /** 125 | * Setter the device token 126 | * @param token 127 | */ 128 | public void setToken(String token) { 129 | this.token = token; 130 | } 131 | 132 | 133 | public void setLastRegister(Timestamp lastRegister) { 134 | this.lastRegister = lastRegister; 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/javapns/devices/implementations/basic/BasicDeviceFactory.java: -------------------------------------------------------------------------------- 1 | package javapns.devices.implementations.basic; 2 | 3 | import java.sql.*; 4 | import java.util.*; 5 | 6 | import javapns.devices.*; 7 | import javapns.devices.exceptions.*; 8 | 9 | /** 10 | * This class implements an in-memory DeviceFactory (backed by a Map). 11 | * Since this class does not persist Device objects, it should not be used in a production environment. 12 | * 13 | * NB : Future Improvement : 14 | * - Add a method to find a device knowing his token 15 | * - Add a method to update a device (timestamp or token) 16 | * - method to compare two devices, and replace when the device token has changed 17 | * 18 | * @author Maxime Peron 19 | * 20 | */ 21 | @Deprecated 22 | public class BasicDeviceFactory implements DeviceFactory { 23 | 24 | /* A map containing all the devices, identified with their id */ 25 | private Map devices; 26 | 27 | /* synclock */ 28 | private static final Object synclock = new Object(); 29 | 30 | 31 | /** 32 | * Constructs a VolatileDeviceFactory 33 | */ 34 | public BasicDeviceFactory() { 35 | this.devices = new HashMap(); 36 | } 37 | 38 | 39 | /** 40 | * Add a device to the map 41 | * @param id The device id 42 | * @param token The device token 43 | * @throws DuplicateDeviceException 44 | * @throws NullIdException 45 | * @throws NullDeviceTokenException 46 | */ 47 | public Device addDevice(String id, String token) throws DuplicateDeviceException, NullIdException, NullDeviceTokenException, Exception { 48 | if ((id == null) || (id.trim().equals(""))) { 49 | throw new NullIdException(); 50 | } else if ((token == null) || (token.trim().equals(""))) { 51 | throw new NullDeviceTokenException(); 52 | } else { 53 | if (!this.devices.containsKey(id)) { 54 | token = token.trim().replace(" ", ""); 55 | BasicDevice device = new BasicDevice(id, token, new Timestamp(Calendar.getInstance().getTime().getTime())); 56 | this.devices.put(id, device); 57 | return device; 58 | } else { 59 | throw new DuplicateDeviceException(); 60 | } 61 | } 62 | } 63 | 64 | 65 | /** 66 | * Get a device according to his id 67 | * @param id The device id 68 | * @return The device 69 | * @throws UnknownDeviceException 70 | * @throws NullIdException 71 | */ 72 | public Device getDevice(String id) throws UnknownDeviceException, NullIdException { 73 | if ((id == null) || (id.trim().equals(""))) { 74 | throw new NullIdException(); 75 | } else { 76 | if (this.devices.containsKey(id)) { 77 | return this.devices.get(id); 78 | } else { 79 | throw new UnknownDeviceException(); 80 | } 81 | } 82 | } 83 | 84 | 85 | /** 86 | * Remove a device 87 | * @param id The device id 88 | * @throws UnknownDeviceException 89 | * @throws NullIdException 90 | */ 91 | public void removeDevice(String id) throws UnknownDeviceException, NullIdException { 92 | if ((id == null) || (id.trim().equals(""))) { 93 | throw new NullIdException(); 94 | } 95 | if (this.devices.containsKey(id)) { 96 | this.devices.remove(id); 97 | } else { 98 | throw new UnknownDeviceException(); 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /src/main/java/javapns/devices/implementations/basic/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | A basic non-persistent implementation for devices. 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/javapns/devices/implementations/jpa/injection.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/java/javapns/devices/implementations/jpa/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | Details on implementing devices and device factories using JPA. 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/javapns/devices/implementations/jpa/readme.txt: -------------------------------------------------------------------------------- 1 | To support JPA: 2 | 3 | 1) Implement javapns.devices.Device as a POJO 4 | 2) Implement javapns.devices.DeviceFactory to hook up to your own JPA-backed DAO 5 | 3) See "injection.xml" file for wiring JavaPNS using Spring 6 | 4) Define a getter/setter pair for pushNotificationManager and feedbackServiceManager on any Spring-managed bean to get access to fully-functional PushNotificationManager and FeedbackServiceManager 7 | 8 | 9 | Notes: 10 | 11 | - AppleNotificationServer and AppleFeedbackServer objects can also be injected instead of using *BasicImpl implementations 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/javapns/devices/implementations/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | Concrete implementations of devices and device factories. 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/javapns/devices/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | Classes representing mobile devices. 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/javapns/feedback/AppleFeedbackServer.java: -------------------------------------------------------------------------------- 1 | package javapns.feedback; 2 | 3 | import javapns.communication.*; 4 | 5 | /** 6 | * Interface representing a connection to an Apple Feedback Server 7 | * 8 | * @author Sylvain Pedneault 9 | */ 10 | public interface AppleFeedbackServer extends AppleServer { 11 | 12 | public static final String PRODUCTION_HOST = "feedback.push.apple.com"; 13 | public static final int PRODUCTION_PORT = 2196; 14 | 15 | public static final String DEVELOPMENT_HOST = "feedback.sandbox.push.apple.com"; 16 | public static final int DEVELOPMENT_PORT = 2196; 17 | 18 | 19 | public String getFeedbackServerHost(); 20 | 21 | 22 | public int getFeedbackServerPort(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/javapns/feedback/AppleFeedbackServerBasicImpl.java: -------------------------------------------------------------------------------- 1 | package javapns.feedback; 2 | 3 | import javapns.communication.*; 4 | import javapns.communication.exceptions.*; 5 | 6 | /** 7 | * Basic implementation of the AppleFeedbackServer interface, 8 | * intended to facilitate rapid deployment. 9 | * 10 | * @author Sylvain Pedneault 11 | */ 12 | public class AppleFeedbackServerBasicImpl extends AppleServerBasicImpl implements AppleFeedbackServer { 13 | 14 | private String host; 15 | private int port; 16 | 17 | 18 | /** 19 | * Communication settings for interacting with Apple's default production or sandbox feedback server. 20 | * This constructor uses the recommended keystore type "PCKS12". 21 | * 22 | * @param keystore The keystore to use (can be a File, an InputStream, a String for a file path, or a byte[] array) 23 | * @param password The keystore's password 24 | * @param production true to use Apple's production servers, false to use the sandbox 25 | * @throws KeystoreException thrown if an error occurs when loading the keystore 26 | */ 27 | public AppleFeedbackServerBasicImpl(Object keystore, String password, boolean production) throws KeystoreException { 28 | this(keystore, password, ConnectionToAppleServer.KEYSTORE_TYPE_PKCS12, production); 29 | } 30 | 31 | 32 | /** 33 | * Communication settings for interacting with Apple's default production or sandbox feedback server. 34 | * 35 | * @param keystore The keystore to use (can be a File, an InputStream, a String for a file path, or a byte[] array) 36 | * @param password The keystore's password 37 | * @param type The keystore's type 38 | * @param production true to use Apple's production servers, false to use the sandbox 39 | * @throws KeystoreException thrown if an error occurs when loading the keystore 40 | */ 41 | public AppleFeedbackServerBasicImpl(Object keystore, String password, String type, boolean production) throws KeystoreException { 42 | this(keystore, password, type, production ? PRODUCTION_HOST : DEVELOPMENT_HOST, production ? PRODUCTION_PORT : DEVELOPMENT_PORT); 43 | } 44 | 45 | 46 | /** 47 | * Communication settings for interacting with a specific Apple Push Notification Feedback Server. 48 | * 49 | * @param keystore The keystore to use (can be a File, an InputStream, a String for a file path, or a byte[] array) 50 | * @param password The keystore's password 51 | * @param type The keystore's type 52 | * @param host A specific APNS host 53 | * @param port A specific APNS port 54 | * @throws KeystoreException thrown if an error occurs when loading the keystore 55 | */ 56 | public AppleFeedbackServerBasicImpl(Object keystore, String password, String type, String host, int port) throws KeystoreException { 57 | super(keystore, password, type); 58 | this.host = host; 59 | this.port = port; 60 | } 61 | 62 | 63 | public String getFeedbackServerHost() { 64 | return host; 65 | } 66 | 67 | 68 | public int getFeedbackServerPort() { 69 | return port; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/javapns/feedback/ConnectionToFeedbackServer.java: -------------------------------------------------------------------------------- 1 | package javapns.feedback; 2 | 3 | import java.security.*; 4 | 5 | import javapns.communication.*; 6 | import javapns.communication.exceptions.*; 7 | import javapns.notification.*; 8 | 9 | /** 10 | * Class representing a connection to a specific Feedback Server. 11 | * 12 | * @author Sylvain Pedneault 13 | */ 14 | public class ConnectionToFeedbackServer extends ConnectionToAppleServer { 15 | 16 | public ConnectionToFeedbackServer(AppleFeedbackServer feedbackServer) throws KeystoreException { 17 | super(feedbackServer); 18 | } 19 | 20 | 21 | public ConnectionToFeedbackServer(AppleNotificationServer server, KeyStore keystore) throws KeystoreException { 22 | super(server, keystore); 23 | } 24 | 25 | 26 | @Override 27 | public String getServerHost() { 28 | return ((AppleFeedbackServer) getServer()).getFeedbackServerHost(); 29 | } 30 | 31 | 32 | @Override 33 | public int getServerPort() { 34 | return ((AppleFeedbackServer) getServer()).getFeedbackServerPort(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/javapns/feedback/FeedbackServiceManager.java: -------------------------------------------------------------------------------- 1 | package javapns.feedback; 2 | 3 | import java.io.*; 4 | import java.sql.*; 5 | import java.util.*; 6 | 7 | import javapns.communication.exceptions.*; 8 | import javapns.devices.*; 9 | import javapns.devices.implementations.basic.*; 10 | 11 | import javax.net.ssl.*; 12 | 13 | import org.apache.log4j.*; 14 | 15 | /** 16 | * Class for interacting with a specific Feedback Service. 17 | * 18 | * @author kljajo, dgardon, Sylvain Pedneault 19 | * 20 | */ 21 | public class FeedbackServiceManager { 22 | 23 | protected static final Logger logger = Logger.getLogger(FeedbackServiceManager.class); 24 | 25 | /* Length of the tuple sent by Apple */ 26 | private static final int FEEDBACK_TUPLE_SIZE = 38; 27 | 28 | @Deprecated 29 | private DeviceFactory deviceFactory; 30 | 31 | 32 | /** 33 | * Constructs a FeedbackServiceManager with a supplied DeviceFactory. 34 | * @deprecated The DeviceFactory-based architecture is deprecated. 35 | */ 36 | @Deprecated 37 | public FeedbackServiceManager(DeviceFactory deviceFactory) { 38 | this.setDeviceFactory(deviceFactory); 39 | } 40 | 41 | 42 | /** 43 | * Constructs a FeedbackServiceManager with a default basic DeviceFactory. 44 | */ 45 | @SuppressWarnings("deprecation") 46 | public FeedbackServiceManager() { 47 | this.setDeviceFactory(new BasicDeviceFactory()); 48 | } 49 | 50 | 51 | /** 52 | * Retrieve all devices which have un-installed the application w/Path to keystore 53 | * 54 | * @param server Connection information for the Apple server 55 | * @return List of Devices 56 | * @throws IOException 57 | * @throws FileNotFoundException 58 | * @throws CertificateException 59 | * @throws NoSuchAlgorithmException 60 | * @throws KeyStoreException 61 | * @throws KeyManagementException 62 | * @throws UnrecoverableKeyException 63 | */ 64 | /** 65 | * @throws KeystoreException 66 | * @throws CommunicationException 67 | */ 68 | public LinkedList getDevices(AppleFeedbackServer server) throws KeystoreException, CommunicationException { 69 | ConnectionToFeedbackServer connectionHelper = new ConnectionToFeedbackServer(server); 70 | SSLSocket socket = connectionHelper.getSSLSocket(); 71 | return getDevices(socket); 72 | } 73 | 74 | 75 | /** 76 | * Retrieves the list of devices from an established SSLSocket. 77 | * 78 | * @param socket 79 | * @return Devices 80 | * @throws CommunicationException 81 | */ 82 | private LinkedList getDevices(SSLSocket socket) throws CommunicationException { 83 | 84 | // Compute 85 | LinkedList listDev = null; 86 | try { 87 | InputStream socketStream = socket.getInputStream(); 88 | 89 | // Read bytes 90 | byte[] b = new byte[1024]; 91 | ByteArrayOutputStream message = new ByteArrayOutputStream(); 92 | int nbBytes = 0; 93 | // socketStream.available can return 0 94 | // http://forums.sun.com/thread.jspa?threadID=5428561 95 | while ((nbBytes = socketStream.read(b, 0, 1024)) != -1) { 96 | message.write(b, 0, nbBytes); 97 | } 98 | 99 | listDev = new LinkedList(); 100 | byte[] listOfDevices = message.toByteArray(); 101 | int nbTuples = listOfDevices.length / FEEDBACK_TUPLE_SIZE; 102 | logger.debug("Found: [" + nbTuples + "]"); 103 | for (int i = 0; i < nbTuples; i++) { 104 | int offset = i * FEEDBACK_TUPLE_SIZE; 105 | 106 | // Build date 107 | int index = 0; 108 | int firstByte = 0; 109 | int secondByte = 0; 110 | int thirdByte = 0; 111 | int fourthByte = 0; 112 | long anUnsignedInt = 0; 113 | 114 | firstByte = (0x000000FF & ((int) listOfDevices[offset])); 115 | secondByte = (0x000000FF & ((int) listOfDevices[offset + 1])); 116 | thirdByte = (0x000000FF & ((int) listOfDevices[offset + 2])); 117 | fourthByte = (0x000000FF & ((int) listOfDevices[offset + 3])); 118 | index = index + 4; 119 | anUnsignedInt = ((long) (firstByte << 24 | secondByte << 16 | thirdByte << 8 | fourthByte)) & 0xFFFFFFFFL; 120 | Timestamp timestamp = new Timestamp(anUnsignedInt * 1000); 121 | 122 | // Build device token length 123 | int deviceTokenLength = listOfDevices[offset + 4] << 8 | listOfDevices[offset + 5]; 124 | 125 | // Build device token 126 | String deviceToken = ""; 127 | int octet = 0; 128 | for (int j = 0; j < 32; j++) { 129 | octet = (0x000000FF & ((int) listOfDevices[offset + 6 + j])); 130 | deviceToken = deviceToken.concat(String.format("%02x", octet)); 131 | } 132 | 133 | // Build device and add to list 134 | /* Create a basic device, as we do not want to go through the factory and create a device in the actual database... */ 135 | Device device = new BasicDevice(); 136 | device.setToken(deviceToken); 137 | device.setLastRegister(timestamp); 138 | listDev.add(device); 139 | logger.info("FeedbackManager retrieves one device : " + timestamp + ";" + deviceTokenLength + ";" + deviceToken + "."); 140 | } 141 | 142 | // Close the socket and return the list 143 | 144 | } catch (Exception e) { 145 | logger.debug("Caught exception fetching devices from Feedback Service"); 146 | throw new CommunicationException("Problem communicating with Feedback service", e); 147 | } 148 | 149 | finally { 150 | try { 151 | socket.close(); 152 | } catch (Exception e) { 153 | } 154 | } 155 | return listDev; 156 | } 157 | 158 | 159 | // /** 160 | // * Set the proxy if needed 161 | // * @param host the proxyHost 162 | // * @param port the proxyPort 163 | // * @deprecated Configuring a proxy with this method affects overall JVM proxy settings. 164 | // * Use AppleFeedbackServer.setProxy(..) to set a proxy for JavaPNS only. 165 | // */ 166 | // public void setProxy(String host, String port) { 167 | // this.proxySet = true; 168 | // 169 | // System.setProperty("http.proxyHost", host); 170 | // System.setProperty("http.proxyPort", port); 171 | // 172 | // System.setProperty("https.proxyHost", host); 173 | // System.setProperty("https.proxyPort", port); 174 | // } 175 | 176 | /** 177 | * 178 | * @param deviceFactory 179 | * @deprecated The DeviceFactory-based architecture is deprecated. 180 | */ 181 | @Deprecated 182 | public void setDeviceFactory(DeviceFactory deviceFactory) { 183 | this.deviceFactory = deviceFactory; 184 | } 185 | 186 | 187 | /** 188 | * 189 | * @return a device factory 190 | * @deprecated The DeviceFactory-based architecture is deprecated. 191 | */ 192 | @Deprecated 193 | public DeviceFactory getDeviceFactory() { 194 | return deviceFactory; 195 | } 196 | 197 | } -------------------------------------------------------------------------------- /src/main/java/javapns/feedback/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | Classes for interacting with the Apple Feedback Service. 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/AppleNotificationServer.java: -------------------------------------------------------------------------------- 1 | package javapns.notification; 2 | 3 | import javapns.communication.*; 4 | 5 | /** 6 | * Interface representing a connection to an Apple Notification Server 7 | * 8 | * @author Sylvain Pedneault 9 | */ 10 | public interface AppleNotificationServer extends AppleServer { 11 | 12 | public static final String PRODUCTION_HOST = "gateway.push.apple.com"; 13 | public static final int PRODUCTION_PORT = 2195; 14 | 15 | public static final String DEVELOPMENT_HOST = "gateway.sandbox.push.apple.com"; 16 | public static final int DEVELOPMENT_PORT = 2195; 17 | 18 | 19 | public String getNotificationServerHost(); 20 | 21 | 22 | public int getNotificationServerPort(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/AppleNotificationServerBasicImpl.java: -------------------------------------------------------------------------------- 1 | package javapns.notification; 2 | 3 | import javapns.communication.*; 4 | import javapns.communication.exceptions.*; 5 | 6 | /** 7 | * Basic implementation of the AppleNotificationServer interface, 8 | * intended to facilitate rapid deployment. 9 | * 10 | * @author Sylvain Pedneault 11 | */ 12 | public class AppleNotificationServerBasicImpl extends AppleServerBasicImpl implements AppleNotificationServer { 13 | 14 | private String host; 15 | private int port; 16 | 17 | 18 | /** 19 | * Communication settings for interacting with Apple's default production or sandbox notification server. 20 | * This constructor uses the recommended keystore type "PCKS12". 21 | * 22 | * @param keystore a keystore containing your private key and the certificate signed by Apple (File, InputStream, byte[], KeyStore or String for a file path) 23 | * @param password the keystore's password 24 | * @param production true to use Apple's production servers, false to use the sandbox 25 | * @throws KeystoreException thrown if an error occurs when loading the keystore 26 | */ 27 | public AppleNotificationServerBasicImpl(Object keystore, String password, boolean production) throws KeystoreException { 28 | this(keystore, password, ConnectionToAppleServer.KEYSTORE_TYPE_PKCS12, production); 29 | } 30 | 31 | 32 | /** 33 | * Communication settings for interacting with Apple's default production or sandbox notification server. 34 | * 35 | * @param keystore a keystore containing your private key and the certificate signed by Apple (File, InputStream, byte[], KeyStore or String for a file path) 36 | * @param password the keystore's password 37 | * @param type the keystore's type 38 | * @param production true to use Apple's production servers, false to use the sandbox 39 | * @throws KeystoreException thrown if an error occurs when loading the keystore 40 | */ 41 | public AppleNotificationServerBasicImpl(Object keystore, String password, String type, boolean production) throws KeystoreException { 42 | this(keystore, password, type, production ? PRODUCTION_HOST : DEVELOPMENT_HOST, production ? PRODUCTION_PORT : DEVELOPMENT_PORT); 43 | } 44 | 45 | 46 | /** 47 | * Communication settings for interacting with a specific Apple Push Notification Server. 48 | * 49 | * @param keystore a keystore containing your private key and the certificate signed by Apple (File, InputStream, byte[], KeyStore or String for a file path) 50 | * @param password the keystore's password 51 | * @param type the keystore's type 52 | * @param host a specific APNS host 53 | * @param port a specific APNS port 54 | * @throws KeystoreException thrown if an error occurs when loading the keystore 55 | */ 56 | public AppleNotificationServerBasicImpl(Object keystore, String password, String type, String host, int port) throws KeystoreException { 57 | super(keystore, password, type); 58 | this.host = host; 59 | this.port = port; 60 | } 61 | 62 | 63 | public String getNotificationServerHost() { 64 | return host; 65 | } 66 | 67 | 68 | public int getNotificationServerPort() { 69 | return port; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/ConnectionToNotificationServer.java: -------------------------------------------------------------------------------- 1 | package javapns.notification; 2 | 3 | import java.security.*; 4 | 5 | import javapns.communication.*; 6 | import javapns.communication.exceptions.*; 7 | 8 | /** 9 | * Connection details specific to the Notification Service. 10 | * 11 | * @author Sylvain Pedneault 12 | */ 13 | public class ConnectionToNotificationServer extends ConnectionToAppleServer { 14 | 15 | public ConnectionToNotificationServer(AppleNotificationServer server) throws KeystoreException { 16 | super(server); 17 | } 18 | 19 | 20 | public ConnectionToNotificationServer(AppleNotificationServer server, KeyStore keystore) throws KeystoreException { 21 | super(server, keystore); 22 | } 23 | 24 | 25 | @Override 26 | public String getServerHost() { 27 | return ((AppleNotificationServer) getServer()).getNotificationServerHost(); 28 | } 29 | 30 | 31 | @Override 32 | public int getServerPort() { 33 | return ((AppleNotificationServer) getServer()).getNotificationServerPort(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/NewsstandNotificationPayload.java: -------------------------------------------------------------------------------- 1 | package javapns.notification; 2 | 3 | import org.json.*; 4 | 5 | /** 6 | * A Newsstand-specific payload compatible with the Apple Push Notification Service. 7 | * 8 | * @author Sylvain Pedneault 9 | */ 10 | public class NewsstandNotificationPayload extends Payload { 11 | 12 | /** 13 | * Create a pre-defined payload with a content-available property set to 1. 14 | * 15 | * @return a ready-to-send newsstand payload 16 | */ 17 | public static NewsstandNotificationPayload contentAvailable() { 18 | NewsstandNotificationPayload payload = complex(); 19 | try { 20 | payload.addContentAvailable(); 21 | } catch (JSONException e) { 22 | } 23 | return payload; 24 | } 25 | 26 | 27 | /** 28 | * Create an empty payload which you can configure later (most users should not use this). 29 | * This method is usually used to create complex or custom payloads. 30 | * Note: the payload actually contains the default "aps" 31 | * dictionary required by Newsstand. 32 | * 33 | * @return a blank payload that can be customized 34 | */ 35 | private static NewsstandNotificationPayload complex() { 36 | NewsstandNotificationPayload payload = new NewsstandNotificationPayload(); 37 | return payload; 38 | } 39 | 40 | /* The application Dictionnary */ 41 | private JSONObject apsDictionary; 42 | 43 | 44 | /** 45 | * Create a default payload with a blank "aps" dictionary. 46 | */ 47 | NewsstandNotificationPayload() { 48 | super(); 49 | this.apsDictionary = new JSONObject(); 50 | try { 51 | JSONObject payload = getPayload(); 52 | payload.put("aps", this.apsDictionary); 53 | } catch (JSONException e) { 54 | e.printStackTrace(); 55 | } 56 | } 57 | 58 | 59 | void addContentAvailable() throws JSONException { 60 | addContentAvailable(1); 61 | } 62 | 63 | 64 | void addContentAvailable(int contentAvailable) throws JSONException { 65 | logger.debug("Adding ContentAvailable [" + contentAvailable + "]"); 66 | this.apsDictionary.put("content-available", contentAvailable); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/PayloadPerDevice.java: -------------------------------------------------------------------------------- 1 | package javapns.notification; 2 | 3 | import javapns.devices.*; 4 | import javapns.devices.exceptions.*; 5 | import javapns.devices.implementations.basic.*; 6 | 7 | /** 8 | * A one-to-one link between a payload and device. 9 | * Provides support for a typical payload-per-device scenario. 10 | * 11 | * @author Sylvain Pedneault 12 | */ 13 | public class PayloadPerDevice { 14 | 15 | private Payload payload; 16 | private Device device; 17 | 18 | 19 | public PayloadPerDevice(Payload payload, String token) throws InvalidDeviceTokenFormatException { 20 | super(); 21 | this.payload = payload; 22 | this.device = new BasicDevice(token); 23 | } 24 | 25 | 26 | public PayloadPerDevice(Payload payload, Device device) { 27 | super(); 28 | this.payload = payload; 29 | this.device = device; 30 | } 31 | 32 | 33 | public Payload getPayload() { 34 | return payload; 35 | } 36 | 37 | 38 | public Device getDevice() { 39 | return device; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/PushNotificationBigPayload.java: -------------------------------------------------------------------------------- 1 | package javapns.notification; 2 | 3 | import org.json.JSONException; 4 | 5 | public class PushNotificationBigPayload extends PushNotificationPayload { 6 | 7 | /* Maximum total length (serialized) of a payload */ 8 | private static final int MAXIMUM_PAYLOAD_LENGTH = 2048; 9 | 10 | /** 11 | * Return the maximum payload size in bytes. 12 | * For APNS payloads, since iOS8, this method returns 2048. 13 | * 14 | * @return the maximum payload size in bytes (2048) 15 | */ 16 | @Override 17 | public int getMaximumPayloadSize() { 18 | return MAXIMUM_PAYLOAD_LENGTH; 19 | } 20 | 21 | public static PushNotificationBigPayload complex() { 22 | return new PushNotificationBigPayload(); 23 | } 24 | 25 | public static PushNotificationBigPayload fromJSON(String rawJSON) throws JSONException { 26 | return new PushNotificationBigPayload(rawJSON); 27 | } 28 | 29 | public PushNotificationBigPayload() { 30 | super(); 31 | } 32 | 33 | public PushNotificationBigPayload(String rawJSON) throws JSONException { 34 | super(rawJSON); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/PushNotificationManager.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernandospr/javapns-jdk16/84de6d9328ab01af92f77cc60c4554de02420909/src/main/java/javapns/notification/PushNotificationManager.java -------------------------------------------------------------------------------- /src/main/java/javapns/notification/PushedNotification.java: -------------------------------------------------------------------------------- 1 | package javapns.notification; 2 | 3 | import java.util.*; 4 | 5 | import javapns.devices.*; 6 | import javapns.notification.exceptions.*; 7 | 8 | /** 9 | *

An object representing the result of a push notification to a specific payload to a single device.

10 | * 11 | *

If any error occurred while trying to push the notification, an exception is attached.

12 | * 13 | *

If Apple's Push Notification Service returned an error-response packet, it is linked to the related PushedNotification 14 | * so you can find out what the actual error was.

15 | * 16 | * @author Sylvain Pedneault 17 | */ 18 | public class PushedNotification { 19 | 20 | private Payload payload; 21 | private Device device; 22 | private ResponsePacket response; 23 | 24 | private int identifier; 25 | private long expiry; 26 | private int transmissionAttempts; 27 | private boolean transmissionCompleted; 28 | 29 | private Exception exception; 30 | 31 | 32 | protected PushedNotification(Device device, Payload payload) { 33 | this.device = device; 34 | this.payload = payload; 35 | } 36 | 37 | 38 | protected PushedNotification(Device device, Payload payload, int identifier) { 39 | this.device = device; 40 | this.payload = payload; 41 | this.identifier = identifier; 42 | } 43 | 44 | 45 | public PushedNotification(Device device, Payload payload, Exception exception) { 46 | this.device = device; 47 | this.payload = payload; 48 | this.exception = exception; 49 | } 50 | 51 | 52 | /** 53 | * Returns the payload that was pushed. 54 | * 55 | * @return the payload that was pushed 56 | */ 57 | public Payload getPayload() { 58 | return payload; 59 | } 60 | 61 | 62 | protected void setPayload(Payload payload) { 63 | this.payload = payload; 64 | } 65 | 66 | 67 | /** 68 | * Returns the device that the payload was pushed to. 69 | * @return the device that the payload was pushed to 70 | */ 71 | public Device getDevice() { 72 | return device; 73 | } 74 | 75 | 76 | protected void setDevice(Device device) { 77 | this.device = device; 78 | } 79 | 80 | 81 | /** 82 | * Returns the connection-unique identifier referred to by 83 | * error-response packets. 84 | * 85 | * @return a connection-unique identifier 86 | */ 87 | public int getIdentifier() { 88 | return identifier; 89 | } 90 | 91 | 92 | protected void setIdentifier(int identifier) { 93 | this.identifier = identifier; 94 | } 95 | 96 | 97 | /** 98 | * Returns the expiration date of the push notification. 99 | * 100 | * @return the expiration date of the push notification. 101 | */ 102 | public long getExpiry() { 103 | return expiry; 104 | } 105 | 106 | 107 | protected void setExpiry(long expiry) { 108 | this.expiry = expiry; 109 | } 110 | 111 | 112 | protected void setTransmissionAttempts(int transmissionAttempts) { 113 | this.transmissionAttempts = transmissionAttempts; 114 | } 115 | 116 | 117 | protected void addTransmissionAttempt() { 118 | transmissionAttempts++; 119 | } 120 | 121 | 122 | /** 123 | * Returns the number of attempts that have been made to transmit the notification. 124 | * @return a number of attempts 125 | */ 126 | public int getTransmissionAttempts() { 127 | return transmissionAttempts; 128 | } 129 | 130 | 131 | /** 132 | * Returns a human-friendly description of the number of attempts made to transmit the notification. 133 | * @return a human-friendly description of the number of attempts made to transmit the notification 134 | */ 135 | public String getLatestTransmissionAttempt() { 136 | if (transmissionAttempts == 0) return "no attempt yet"; 137 | switch (transmissionAttempts) { 138 | case 0: 139 | return "no attempt yet"; 140 | case 1: 141 | return "first attempt"; 142 | case 2: 143 | return "second attempt"; 144 | case 3: 145 | return "third attempt"; 146 | case 4: 147 | return "fourth attempt"; 148 | default: 149 | return "attempt #" + transmissionAttempts; 150 | } 151 | } 152 | 153 | 154 | protected void setTransmissionCompleted(boolean completed) { 155 | this.transmissionCompleted = completed; 156 | } 157 | 158 | 159 | /** 160 | * Indicates if the notification has been streamed successfully to Apple's server. 161 | * This does not indicate if an error-response was received or not, but simply 162 | * that the library successfully completed the transmission of the notification to 163 | * Apple's server. 164 | * @return true if the notification was successfully streamed to Apple, false otherwise 165 | */ 166 | public boolean isTransmissionCompleted() { 167 | return transmissionCompleted; 168 | } 169 | 170 | 171 | protected void setResponse(ResponsePacket response) { 172 | this.response = response; 173 | if (response != null && exception == null) exception = new ErrorResponsePacketReceivedException(response); 174 | } 175 | 176 | 177 | /** 178 | * If a response packet regarding this notification was received, 179 | * this method returns it. Otherwise it returns null. 180 | * 181 | * @return a response packet, if one was received for this notification 182 | */ 183 | public ResponsePacket getResponse() { 184 | return response; 185 | } 186 | 187 | 188 | /** 189 | *

Returns true if no response packet was received for this notification, 190 | * or if one was received but is not an error-response (ie command 8), 191 | * or if one was received but its status is 0 (no error occurred).

192 | * 193 | *

Returns false if an error-response packet is attached and has 194 | * a non-zero status code.

195 | * 196 | *

Returns false if an exception is attached.

197 | * 198 | *

Make sure you use the Feedback Service to cleanup your list of 199 | * invalid device tokens, as Apple's documentation says.

200 | * 201 | * @return true if push was successful, false otherwise 202 | */ 203 | public boolean isSuccessful() { 204 | if (!transmissionCompleted) return false; 205 | if (response == null) return true; 206 | if (!response.isValidErrorMessage()) return true; 207 | return false; 208 | } 209 | 210 | 211 | /** 212 | * Filters a list of pushed notifications and returns only the ones that were successful. 213 | * 214 | * @param notifications a list of pushed notifications 215 | * @return a filtered list containing only notifications that were succcessful 216 | */ 217 | public static List findSuccessfulNotifications(List notifications) { 218 | List filteredList = new Vector(); 219 | for (PushedNotification notification : notifications) { 220 | if (notification.isSuccessful()) filteredList.add(notification); 221 | } 222 | return filteredList; 223 | } 224 | 225 | 226 | /** 227 | * Filters a list of pushed notifications and returns only the ones that failed. 228 | * 229 | * @param notifications a list of pushed notifications 230 | * @return a filtered list containing only notifications that were not successful 231 | */ 232 | public static List findFailedNotifications(List notifications) { 233 | List filteredList = new Vector(); 234 | for (PushedNotification notification : notifications) { 235 | if (!notification.isSuccessful()) { 236 | filteredList.add(notification); 237 | } 238 | } 239 | return filteredList; 240 | } 241 | 242 | 243 | /** 244 | * Returns a human-friendly description of this pushed notification. 245 | */ 246 | @Override 247 | public String toString() { 248 | StringBuilder msg = new StringBuilder(); 249 | msg.append("[" + identifier + "]"); 250 | msg.append(transmissionCompleted ? " transmitted " + payload + " on " + getLatestTransmissionAttempt() : " not transmitted"); 251 | msg.append(" to token " + device.getToken().substring(0, 5) + ".." + device.getToken().substring(59, 64)); 252 | if (response != null) { 253 | msg.append(" " + response.getMessage()); 254 | } 255 | if (exception != null) { 256 | msg.append(" " + exception); 257 | } 258 | return msg.toString(); 259 | } 260 | 261 | 262 | void setException(Exception exception) { 263 | this.exception = exception; 264 | } 265 | 266 | 267 | /** 268 | * Get the exception that occurred while trying to push this notification, if any. 269 | * @return an exception (if any was thrown) 270 | */ 271 | public Exception getException() { 272 | return exception; 273 | } 274 | 275 | } 276 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/PushedNotifications.java: -------------------------------------------------------------------------------- 1 | package javapns.notification; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | *

A list of PushedNotification objects.

7 | * 8 | *

This list can be configured to retain a maximum number of objects. When that maximum is reached, older objects are removed from the list before new ones are added.

9 | * 10 | *

Internally, this list extends Vector.

11 | * 12 | * @author Sylvain Pedneault 13 | */ 14 | public class PushedNotifications extends Vector implements List { 15 | 16 | private static final long serialVersionUID = 1L; 17 | 18 | private int maxRetained = 1000; 19 | 20 | 21 | /** 22 | * Construct an empty list of PushedNotification objects. 23 | */ 24 | public PushedNotifications() { 25 | } 26 | 27 | 28 | /** 29 | * Construct an empty list of PushedNotification objects with a suggested initial capacity. 30 | * 31 | * @param capacity 32 | */ 33 | public PushedNotifications(int capacity) { 34 | super(capacity); 35 | } 36 | 37 | 38 | /** 39 | * Construct an empty list of PushedNotification objects, and copy the maxRetained property from the provided parent list. 40 | * 41 | * @param parent 42 | */ 43 | public PushedNotifications(PushedNotifications parent) { 44 | this.maxRetained = parent.getMaxRetained(); 45 | } 46 | 47 | 48 | /** 49 | * Filter a list of pushed notifications and return only the ones that were successful. 50 | * 51 | * @return a filtered list containing only notifications that were succcessful 52 | */ 53 | public PushedNotifications getSuccessfulNotifications() { 54 | PushedNotifications filteredList = new PushedNotifications(this); 55 | for (PushedNotification notification : this) { 56 | if (notification.isSuccessful()) filteredList.add(notification); 57 | } 58 | return filteredList; 59 | } 60 | 61 | 62 | /** 63 | * Filter a list of pushed notifications and return only the ones that failed. 64 | * 65 | * @return a filtered list containing only notifications that were not successful 66 | */ 67 | public PushedNotifications getFailedNotifications() { 68 | PushedNotifications filteredList = new PushedNotifications(this); 69 | for (PushedNotification notification : this) { 70 | if (!notification.isSuccessful()) { 71 | filteredList.add(notification); 72 | } 73 | } 74 | return filteredList; 75 | } 76 | 77 | 78 | @Override 79 | public synchronized boolean add(PushedNotification notification) { 80 | prepareAdd(1); 81 | return super.add(notification); 82 | } 83 | 84 | 85 | @Override 86 | public synchronized void addElement(PushedNotification notification) { 87 | prepareAdd(1); 88 | super.addElement(notification); 89 | } 90 | 91 | 92 | @Override 93 | public synchronized boolean addAll(Collection notifications) { 94 | prepareAdd(notifications.size()); 95 | return super.addAll(notifications); 96 | } 97 | 98 | 99 | private void prepareAdd(int n) { 100 | int size = size(); 101 | if (size + n > maxRetained) { 102 | for (int i = 0; i < n; i++) 103 | remove(0); 104 | } 105 | } 106 | 107 | 108 | /** 109 | * Set the maximum number of objects that this list retains. 110 | * When this maximum is reached, older objects are removed from the list before new ones are added. 111 | * 112 | * @param maxRetained the maxRetained value currently configured (default is 1000) 113 | */ 114 | public void setMaxRetained(int maxRetained) { 115 | this.maxRetained = maxRetained; 116 | } 117 | 118 | 119 | /** 120 | * Get the maximum number of objects that this list retains. 121 | * @return the maximum number of objects that this list retains 122 | */ 123 | public int getMaxRetained() { 124 | return maxRetained; 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/ResponsePacket.java: -------------------------------------------------------------------------------- 1 | package javapns.notification; 2 | 3 | /** 4 | * A response packet, as described in Apple's enhanced notification format. 5 | * 6 | * @author Sylvain Pedneault 7 | */ 8 | public class ResponsePacket { 9 | 10 | private int command; 11 | private int status; 12 | private int identifier; 13 | 14 | 15 | protected ResponsePacket() { 16 | } 17 | 18 | 19 | protected ResponsePacket(int command, int status, int identifier) { 20 | this.command = command; 21 | this.status = status; 22 | this.identifier = identifier; 23 | } 24 | 25 | 26 | protected void linkToPushedNotification(PushNotificationManager notificationManager) { 27 | PushedNotification notification = null; 28 | try { 29 | notification = notificationManager.getPushedNotifications().get(identifier); 30 | if (notification != null) { 31 | notification.setResponse(this); 32 | } 33 | } catch (Exception e) { 34 | } 35 | } 36 | 37 | 38 | /** 39 | * Returns the response's command number. It should be 8 for all error responses. 40 | * 41 | * @return the response's command number (which should be 8) 42 | */ 43 | public int getCommand() { 44 | return command; 45 | } 46 | 47 | 48 | protected void setCommand(int command) { 49 | this.command = command; 50 | } 51 | 52 | 53 | /** 54 | * Determine if this packet is an error-response packet. 55 | * @return true if command number is 8, false otherwise 56 | */ 57 | public boolean isErrorResponsePacket() { 58 | return command == 8; 59 | } 60 | 61 | 62 | /** 63 | * Returns the response's status code (see getMessage() for a human-friendly status message instead). 64 | * @return the response's status code 65 | */ 66 | public int getStatus() { 67 | return status; 68 | } 69 | 70 | 71 | protected void setStatus(int status) { 72 | this.status = status; 73 | } 74 | 75 | 76 | /** 77 | * Determine if this packet is a valid error-response packet. 78 | * To be valid, it must be an error-response packet (command number 8) and it must have a non-zero status code. 79 | * @return true if command number is 8 and status code is not 0, false otherwise 80 | */ 81 | public boolean isValidErrorMessage() { 82 | if (!isErrorResponsePacket()) return false; 83 | return status != 0; 84 | } 85 | 86 | 87 | /** 88 | * Returns the response's identifier, which matches the pushed notification's. 89 | * 90 | * @return the response's identifier 91 | */ 92 | public int getIdentifier() { 93 | return identifier; 94 | } 95 | 96 | 97 | protected void setIdentifier(int identifier) { 98 | this.identifier = identifier; 99 | } 100 | 101 | 102 | /** 103 | * Returns a humand-friendly error message, as documented by Apple. 104 | * 105 | * @return a humand-friendly error message 106 | */ 107 | public String getMessage() { 108 | if (command == 8) { 109 | String prefix = "APNS: [" + identifier + "] "; //APNS ERROR FOR MESSAGE ID #" + identifier + ": "; 110 | if (status == 0) return prefix + "No errors encountered"; 111 | if (status == 1) return prefix + "Processing error"; 112 | if (status == 2) return prefix + "Missing device token"; 113 | if (status == 3) return prefix + "Missing topic"; 114 | if (status == 4) return prefix + "Missing payload"; 115 | if (status == 5) return prefix + "Invalid token size"; 116 | if (status == 6) return prefix + "Invalid topic size"; 117 | if (status == 7) return prefix + "Invalid payload size"; 118 | if (status == 8) return prefix + "Invalid token"; 119 | if (status == 255) return prefix + "None (unknown)"; 120 | return prefix + "Undocumented status code: " + status; 121 | } 122 | return "APNS: Undocumented response command: " + command; 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/ResponsePacketReader.java: -------------------------------------------------------------------------------- 1 | package javapns.notification; 2 | 3 | import java.io.*; 4 | import java.net.*; 5 | import java.util.*; 6 | 7 | /** 8 | * Class for reading response packets from an APNS connection. 9 | * See Apple's documentation on enhanced notification format. 10 | * 11 | * @author Sylvain Pedneault 12 | */ 13 | class ResponsePacketReader { 14 | 15 | /* The number of seconds to wait for a response */ 16 | private static final int TIMEOUT = 5 * 1000; 17 | 18 | 19 | /** 20 | * Read response packets from the current APNS connection and process them. 21 | * 22 | * @param notificationManager 23 | * @return the number of response packets received and processed 24 | */ 25 | public static int processResponses(PushNotificationManager notificationManager) { 26 | List responses = readResponses(notificationManager.getActiveSocket()); 27 | handleResponses(responses, notificationManager); 28 | return responses.size(); 29 | } 30 | 31 | 32 | /** 33 | * Read raw response packets from the provided socket. 34 | * 35 | * Note: this method automatically sets the socket's timeout 36 | * to TIMEOUT, so not to block the socket's input stream. 37 | * 38 | * @param socket 39 | * @return 40 | */ 41 | private static List readResponses(Socket socket) { 42 | List responses = new Vector(); 43 | int previousTimeout = 0; 44 | try { 45 | /* Set socket timeout to avoid getting stuck on read() */ 46 | try { 47 | previousTimeout = socket.getSoTimeout(); 48 | socket.setSoTimeout(TIMEOUT); 49 | } catch (Exception e) { 50 | } 51 | InputStream input = socket.getInputStream(); 52 | while (true) { 53 | ResponsePacket packet = readResponsePacketData(input); 54 | if (packet != null) responses.add(packet); 55 | else break; 56 | } 57 | 58 | } catch (Exception e) { 59 | /* Ignore exception, as we are expecting timeout exceptions because Apple might not reply anything */ 60 | //System.out.println(e); 61 | } 62 | /* Reset socket timeout, just in case */ 63 | try { 64 | socket.setSoTimeout(previousTimeout); 65 | } catch (Exception e) { 66 | } 67 | //System.out.println("Received "+responses.size()+" response packets"); 68 | return responses; 69 | } 70 | 71 | 72 | private static void handleResponses(List responses, PushNotificationManager notificationManager) { 73 | Map envelopes = notificationManager.getPushedNotifications(); 74 | for (ResponsePacket response : responses) { 75 | response.linkToPushedNotification(notificationManager); 76 | } 77 | } 78 | 79 | 80 | private static ResponsePacket readResponsePacketData(InputStream input) throws IOException { 81 | int command = input.read(); 82 | if (command < 0) return null; 83 | int status = input.read(); 84 | if (status < 0) return null; 85 | 86 | int identifier_byte1 = input.read(); 87 | if (identifier_byte1 < 0) return null; 88 | int identifier_byte2 = input.read(); 89 | if (identifier_byte2 < 0) return null; 90 | int identifier_byte3 = input.read(); 91 | if (identifier_byte3 < 0) return null; 92 | int identifier_byte4 = input.read(); 93 | if (identifier_byte4 < 0) return null; 94 | int identifier = (identifier_byte1 << 24) + (identifier_byte2 << 16) + (identifier_byte3 << 8) + (identifier_byte4); 95 | return new ResponsePacket(command, status, identifier); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/exceptions/ErrorResponsePacketReceivedException.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.exceptions; 2 | 3 | import javapns.notification.*; 4 | 5 | /** 6 | * Thrown when an error response packet was received from an APNS server. 7 | * 8 | * @author Sylvain Pedneault 9 | */ 10 | @SuppressWarnings("serial") 11 | public class ErrorResponsePacketReceivedException extends Exception { 12 | 13 | private ResponsePacket packet; 14 | 15 | 16 | public ErrorResponsePacketReceivedException(ResponsePacket packet) { 17 | super(String.format("An error response packet was received from the APNS server: %s", packet.getMessage())); 18 | this.packet = packet; 19 | } 20 | 21 | 22 | public ResponsePacket getPacket() { 23 | return packet; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/exceptions/PayloadAlertAlreadyExistsException.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.exceptions; 2 | 3 | import org.json.*; 4 | 5 | /** 6 | * Thrown when a payload exceeds the maximum size allowed. 7 | * @author Sylvain Pedneault 8 | * 9 | */ 10 | @SuppressWarnings("serial") 11 | public class PayloadAlertAlreadyExistsException extends JSONException { 12 | 13 | /** 14 | * Default constructor 15 | */ 16 | public PayloadAlertAlreadyExistsException() { 17 | super("Payload alert already exists"); 18 | } 19 | 20 | 21 | /** 22 | * Constructor with custom message 23 | * @param message 24 | */ 25 | public PayloadAlertAlreadyExistsException(String message) { 26 | super(message); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/exceptions/PayloadIsEmptyException.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.exceptions; 2 | 3 | /** 4 | * Thrown when a payload is empty. 5 | * @author Sylvain Pedneault 6 | * 7 | */ 8 | @SuppressWarnings("serial") 9 | public class PayloadIsEmptyException extends Exception { 10 | 11 | public PayloadIsEmptyException() { 12 | super("Payload is empty"); 13 | } 14 | 15 | 16 | /** 17 | * Constructor with custom message 18 | * @param message 19 | */ 20 | public PayloadIsEmptyException(String message) { 21 | super(message); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/exceptions/PayloadMaxSizeExceededException.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.exceptions; 2 | 3 | /** 4 | * Thrown when a payload exceeds the maximum size allowed. 5 | * @author Sylvain Pedneault 6 | * 7 | */ 8 | @SuppressWarnings("serial") 9 | public class PayloadMaxSizeExceededException extends Exception { 10 | 11 | /** 12 | * Default constructor 13 | */ 14 | public PayloadMaxSizeExceededException() { 15 | super("Total payload size exceeds allowed limit"); 16 | } 17 | 18 | 19 | public PayloadMaxSizeExceededException(int maxSize) { 20 | super(String.format("Total payload size exceeds allowed limit (%s bytes max)", maxSize)); 21 | } 22 | 23 | 24 | public PayloadMaxSizeExceededException(int maxSize, int currentSize) { 25 | super(String.format("Total payload size exceeds allowed limit (payload is %s bytes, limit is %s)", currentSize, maxSize)); 26 | } 27 | 28 | 29 | /** 30 | * Constructor with custom message 31 | * @param message 32 | */ 33 | public PayloadMaxSizeExceededException(String message) { 34 | super(message); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/exceptions/PayloadMaxSizeProbablyExceededException.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.exceptions; 2 | 3 | import org.json.*; 4 | 5 | /** 6 | * Thrown when a payload is expected to exceed the maximum size allowed after adding a given property. 7 | * Invoke payload.setPayloadSizeEstimatedWhenAdding(false) to disable this automatic checking. 8 | * 9 | * @author Sylvain Pedneault 10 | */ 11 | @SuppressWarnings("serial") 12 | public class PayloadMaxSizeProbablyExceededException extends JSONException { 13 | 14 | /** 15 | * Default constructor 16 | */ 17 | public PayloadMaxSizeProbablyExceededException() { 18 | super("Total payload size will most likely exceed allowed limit"); 19 | } 20 | 21 | 22 | public PayloadMaxSizeProbablyExceededException(int maxSize) { 23 | super(String.format("Total payload size will most likely exceed allowed limit (%s bytes max)", maxSize)); 24 | } 25 | 26 | 27 | public PayloadMaxSizeProbablyExceededException(int maxSize, int estimatedSize) { 28 | super(String.format("Total payload size will most likely exceed allowed limit (estimated to become %s bytes, limit is %s)", estimatedSize, maxSize)); 29 | } 30 | 31 | 32 | /** 33 | * Constructor with custom message 34 | * @param message 35 | */ 36 | public PayloadMaxSizeProbablyExceededException(String message) { 37 | super(message); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/exceptions/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | Notification-related exceptions thrown by the javapns library. 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/management/APNPayload.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.management; 2 | 3 | import java.util.*; 4 | 5 | import org.json.*; 6 | 7 | /** 8 | * An MDM payload for APN (Access Point Name). 9 | * 10 | * @author Sylvain Pedneault 11 | */ 12 | public class APNPayload extends MobileConfigPayload { 13 | 14 | public APNPayload(int payloadVersion, String payloadOrganization, String payloadIdentifier, String payloadDisplayName, Map defaultsData, String defaultsDomainName, Map[] apns, String apn, String username) throws JSONException { 15 | super(payloadVersion, "com.apple.apn.managed", payloadOrganization, payloadIdentifier, payloadDisplayName); 16 | JSONObject payload = getPayload(); 17 | payload.put("DefaultsData", defaultsData); 18 | payload.put("defaultsDomainName", defaultsDomainName); 19 | for (Map apnsEntry : apns) 20 | payload.put("apns", apnsEntry); 21 | payload.put("apn", apn); 22 | payload.put("username", username); 23 | } 24 | 25 | 26 | public void setPassword(APNPayload value) throws JSONException { 27 | getPayload().put("password", value); 28 | } 29 | 30 | 31 | public void setProxy(String value) throws JSONException { 32 | getPayload().put("proxy", value); 33 | } 34 | 35 | 36 | public void setProxyPort(int value) throws JSONException { 37 | getPayload().put("proxyPort", value); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/management/CalDAVPayload.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.management; 2 | 3 | import org.json.*; 4 | 5 | /** 6 | * An MDM payload for CalDAV. 7 | * 8 | * @author Sylvain Pedneault 9 | */ 10 | public class CalDAVPayload extends MobileConfigPayload { 11 | 12 | public CalDAVPayload(int payloadVersion, String payloadOrganization, String payloadIdentifier, String payloadDisplayName, String calDAVHostName, String calDAVUsername, boolean calDAVUseSSL) throws JSONException { 13 | super(payloadVersion, "com.apple.caldav.account", payloadOrganization, payloadIdentifier, payloadDisplayName); 14 | JSONObject payload = getPayload(); 15 | payload.put("CalDAVHostName", calDAVHostName); 16 | payload.put("CalDAVUsername", calDAVUsername); 17 | payload.put("CalDAVUseSSL", calDAVUseSSL); 18 | } 19 | 20 | 21 | public void setCalDAVAccountDescription(String value) throws JSONException { 22 | getPayload().put("CalDAVAccountDescription", value); 23 | } 24 | 25 | 26 | public void setCalDAVPassword(String value) throws JSONException { 27 | getPayload().put("CalDAVPassword", value); 28 | } 29 | 30 | 31 | public void setCalDAVPort(int value) throws JSONException { 32 | getPayload().put("CalDAVPort", value); 33 | } 34 | 35 | 36 | public void setCalDAVPrincipalURL(String value) throws JSONException { 37 | getPayload().put("CalDAVPrincipalURL", value); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/management/CalendarSubscriptionPayload.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.management; 2 | 3 | import org.json.*; 4 | 5 | /** 6 | * An MDM payload for CalendarSubscription. 7 | * 8 | * @author Sylvain Pedneault 9 | */ 10 | public class CalendarSubscriptionPayload extends MobileConfigPayload { 11 | 12 | public CalendarSubscriptionPayload(int payloadVersion, String payloadOrganization, String payloadIdentifier, String payloadDisplayName, String subCalAccountHostName, boolean subCalAccountUseSSL) throws JSONException { 13 | super(payloadVersion, "com.apple.caldav.account", payloadOrganization, payloadIdentifier, payloadDisplayName); 14 | JSONObject payload = getPayload(); 15 | payload.put("SubCalAccountHostName", subCalAccountHostName); 16 | payload.put("SubCalAccountUseSSL", subCalAccountUseSSL); 17 | } 18 | 19 | 20 | public void setSubCalAccountDescription(String value) throws JSONException { 21 | getPayload().put("SubCalAccountDescription", value); 22 | } 23 | 24 | 25 | public void setSubCalAccountUsername(String value) throws JSONException { 26 | getPayload().put("SubCalAccountUsername", value); 27 | } 28 | 29 | 30 | public void setSubCalAccountPassword(String value) throws JSONException { 31 | getPayload().put("SubCalAccountPassword", value); 32 | } 33 | 34 | 35 | public void setSubCalAccountUseSSL(boolean value) throws JSONException { 36 | getPayload().put("SubCalAccountUseSSL", value); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/management/EmailPayload.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.management; 2 | 3 | import org.json.*; 4 | 5 | /** 6 | * An MDM payload for Email. 7 | * 8 | * @author Sylvain Pedneault 9 | */ 10 | public class EmailPayload extends MobileConfigPayload { 11 | 12 | public EmailPayload(int payloadVersion, String payloadOrganization, String payloadIdentifier, String payloadDisplayName, String emailAccountType, String emailAddress, String incomingMailServerAuthentication, String incomingMailServerHostName, String incomingMailServerUsername, String outgoingMailServerAuthentication, String outgoingMailServerHostName, String outgoingMailServerUsername) throws JSONException { 13 | super(payloadVersion, "com.apple.mail.managed", payloadOrganization, payloadIdentifier, payloadDisplayName); 14 | JSONObject payload = getPayload(); 15 | payload.put("EmailAccountType", emailAccountType); 16 | payload.put("EmailAddress", emailAddress); 17 | payload.put("IncomingMailServerAuthentication", incomingMailServerAuthentication); 18 | payload.put("IncomingMailServerHostName", incomingMailServerHostName); 19 | payload.put("IncomingMailServerUsername", incomingMailServerUsername); 20 | payload.put("OutgoingMailServerAuthentication", outgoingMailServerAuthentication); 21 | payload.put("OutgoingMailServerHostName", outgoingMailServerHostName); 22 | payload.put("OutgoingMailServerUsername", outgoingMailServerUsername); 23 | } 24 | 25 | 26 | public void setEmailAccountDescription(String value) throws JSONException { 27 | getPayload().put("EmailAccountDescription", value); 28 | } 29 | 30 | 31 | public void setEmailAccountName(String value) throws JSONException { 32 | getPayload().put("EmailAccountName", value); 33 | } 34 | 35 | 36 | public void setIncomingMailServerPortNumber(int value) throws JSONException { 37 | getPayload().put("IncomingMailServerPortNumber", value); 38 | } 39 | 40 | 41 | public void setIncomingMailServerUseSSL(boolean value) throws JSONException { 42 | getPayload().put("IncomingMailServerUseSSL", value); 43 | } 44 | 45 | 46 | public void setIncomingPassword(String value) throws JSONException { 47 | getPayload().put("IncomingPassword", value); 48 | } 49 | 50 | 51 | public void setOutgoingPassword(String value) throws JSONException { 52 | getPayload().put("OutgoingPassword", value); 53 | } 54 | 55 | 56 | public void setOutgoingPasswwordSameAsIncomingPassword(boolean value) throws JSONException { 57 | getPayload().put("OutgoingPasswwordSameAsIncomingPassword", value); 58 | } 59 | 60 | 61 | public void setOutgoingMailServerPortNumber(int value) throws JSONException { 62 | getPayload().put("OutgoingMailServerPortNumber", value); 63 | } 64 | 65 | 66 | public void setOutgoingMailServerUseSSL(boolean value) throws JSONException { 67 | getPayload().put("OutgoingMailServerUseSSL", value); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/management/LDAPPayload.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.management; 2 | 3 | import org.json.*; 4 | 5 | /** 6 | * An MDM payload for LDAP. 7 | * 8 | * @author Sylvain Pedneault 9 | */ 10 | public class LDAPPayload extends MobileConfigPayload { 11 | 12 | public LDAPPayload(int payloadVersion, String payloadOrganization, String payloadIdentifier, String payloadDisplayName, String ldapAccountHostName, boolean ldapAccountUseSSL) throws JSONException { 13 | super(payloadVersion, "com.apple.webClip.managed", payloadOrganization, payloadIdentifier, payloadDisplayName); 14 | JSONObject payload = getPayload(); 15 | payload.put("LDAPAccountHostName", ldapAccountHostName); 16 | payload.put("LDAPAccountUseSSL", ldapAccountUseSSL); 17 | } 18 | 19 | 20 | public void setLDAPAccountDescription(boolean value) throws JSONException { 21 | getPayload().put("LDAPAccountDescription", value); 22 | } 23 | 24 | 25 | public void setLDAPAccountUserName(boolean value) throws JSONException { 26 | getPayload().put("LDAPAccountUserName", value); 27 | } 28 | 29 | 30 | public void setLDAPAccountPassword(boolean value) throws JSONException { 31 | getPayload().put("LDAPAccountPassword", value); 32 | } 33 | 34 | 35 | public JSONObject addSearchSettings(String ldapSearchSettingSearchBase, String ldapSearchSettingScope) throws JSONException { 36 | return addSearchSettings(ldapSearchSettingSearchBase, ldapSearchSettingScope, null); 37 | } 38 | 39 | 40 | public JSONObject addSearchSettings(String ldapSearchSettingSearchBase, int ldapSearchSettingScope) throws JSONException { 41 | return addSearchSettings(ldapSearchSettingSearchBase, ldapSearchSettingScope, null); 42 | } 43 | 44 | 45 | public JSONObject addSearchSettings(String ldapSearchSettingSearchBase, int ldapSearchSettingScope, String ldapSearchSettingDescription) throws JSONException { 46 | return addSearchSettings(ldapSearchSettingSearchBase, ldapSearchSettingScope == 0 ? "LDAPSearchSettingScopeBase" : ldapSearchSettingScope == 1 ? "LDAPSearchSettingScopeBase" : "LDAPSearchSettingScopeSubtree", ldapSearchSettingDescription); 47 | } 48 | 49 | 50 | public JSONObject addSearchSettings(String ldapSearchSettingSearchBase, String ldapSearchSettingScope, String ldapSearchSettingDescription) throws JSONException { 51 | JSONObject payload = getPayload(); 52 | JSONObject searchSettings = new JSONObject(); 53 | payload.put("LDAPSearchSettings", searchSettings); 54 | searchSettings.put("LDAPSearchSettingSearchBase", ldapSearchSettingSearchBase); 55 | searchSettings.put("LDAPSearchSettingScope", ldapSearchSettingScope); 56 | if (ldapSearchSettingDescription != null) searchSettings.put("LDAPSearchSettingDescription", ldapSearchSettingDescription); 57 | return searchSettings; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/management/MobileConfigPayload.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.management; 2 | 3 | import javapns.notification.*; 4 | 5 | import org.json.*; 6 | 7 | /** 8 | * A payload template compatible with Apple Mobile Device Management's Config Payload specification (beta version). 9 | * 10 | * @author Sylvain Pedneault 11 | */ 12 | public abstract class MobileConfigPayload extends Payload { 13 | 14 | private static long serialuuid = 10000000; 15 | 16 | 17 | private static String generateUUID() { 18 | return System.nanoTime() + "." + (++serialuuid); 19 | } 20 | 21 | 22 | public MobileConfigPayload(int payloadVersion, String payloadType, String payloadOrganization, String payloadIdentifier, String payloadDisplayName) throws JSONException { 23 | this(payloadVersion, generateUUID(), payloadType, payloadOrganization, payloadIdentifier, payloadDisplayName); 24 | } 25 | 26 | 27 | public MobileConfigPayload(int payloadVersion, String payloadUUID, String payloadType, String payloadOrganization, String payloadIdentifier, String payloadDisplayName) throws JSONException { 28 | super(); 29 | JSONObject payload = getPayload(); 30 | payload.put("PayloadVersion", payloadVersion); 31 | payload.put("PayloadUUID", payloadUUID); 32 | payload.put("PayloadType", payloadType); 33 | payload.put("PayloadOrganization", payloadOrganization); 34 | payload.put("PayloadIdentifier", payloadIdentifier); 35 | payload.put("PayloadDisplayName", payloadDisplayName); 36 | } 37 | 38 | 39 | public void setPayloadDescription(String description) throws JSONException { 40 | getPayload().put("PayloadDescription", description); 41 | } 42 | 43 | 44 | public void setPayloadRemovalDisallowed(boolean disallowed) throws JSONException { 45 | getPayload().put("PayloadRemovalDisallowed", disallowed); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/management/PasswordPolicyPayload.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.management; 2 | 3 | import org.json.*; 4 | 5 | /** 6 | * An MDM payload for PasswordPolicy. 7 | * 8 | * @author Sylvain Pedneault 9 | */ 10 | public class PasswordPolicyPayload extends MobileConfigPayload { 11 | 12 | public PasswordPolicyPayload(int payloadVersion, String payloadOrganization, String payloadIdentifier, String payloadDisplayName) throws JSONException { 13 | super(payloadVersion, "com.apple.mobiledevice.passwordpolicy", payloadOrganization, payloadIdentifier, payloadDisplayName); 14 | } 15 | 16 | 17 | public void setAllowSimple(boolean value) throws JSONException { 18 | getPayload().put("allowSimple", value); 19 | } 20 | 21 | 22 | public void setForcePIN(boolean value) throws JSONException { 23 | getPayload().put("forcePIN", value); 24 | } 25 | 26 | 27 | public void setMaxFailedAttempts(int value) throws JSONException { 28 | getPayload().put("maxFailedAttempts", value); 29 | } 30 | 31 | 32 | public void setMaxInactivity(int value) throws JSONException { 33 | getPayload().put("maxInactivity", value); 34 | } 35 | 36 | 37 | public void setMaxPINAgeInDays(int value) throws JSONException { 38 | getPayload().put("maxPINAgeInDays", value); 39 | } 40 | 41 | 42 | public void setMinComplexChars(int value) throws JSONException { 43 | getPayload().put("minComplexChars", value); 44 | } 45 | 46 | 47 | public void setMinLength(int value) throws JSONException { 48 | getPayload().put("minLength", value); 49 | } 50 | 51 | 52 | public void setRequireAlphanumeric(boolean value) throws JSONException { 53 | getPayload().put("requireAlphanumeric", value); 54 | } 55 | 56 | 57 | public void setPinHistory(int value) throws JSONException { 58 | getPayload().put("pinHistory", value); 59 | } 60 | 61 | 62 | public void setManualFetchingWhenRoaming(boolean value) throws JSONException { 63 | getPayload().put("manualFetchingWhenRoaming", value); 64 | } 65 | 66 | 67 | public void setMaxGracePeriod(int value) throws JSONException { 68 | getPayload().put("maxGracePeriod", value); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/management/RemovalPasswordPayload.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.management; 2 | 3 | import org.json.*; 4 | 5 | /** 6 | * An MDM payload for RemovalPassword. 7 | * 8 | * @author Sylvain Pedneault 9 | */ 10 | public class RemovalPasswordPayload extends MobileConfigPayload { 11 | 12 | public RemovalPasswordPayload(int payloadVersion, String payloadOrganization, String payloadIdentifier, String payloadDisplayName) throws JSONException { 13 | super(payloadVersion, "com.apple.profileRemovalPassword", payloadOrganization, payloadIdentifier, payloadDisplayName); 14 | } 15 | 16 | 17 | public void setRemovalPasword(String value) throws JSONException { 18 | getPayload().put("RemovalPassword", value); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/management/RestrictionsPayload.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.management; 2 | 3 | import org.json.*; 4 | 5 | /** 6 | * An MDM payload for Restrictions. 7 | * 8 | * @author Sylvain Pedneault 9 | */ 10 | public class RestrictionsPayload extends MobileConfigPayload { 11 | 12 | public RestrictionsPayload(int payloadVersion, String payloadOrganization, String payloadIdentifier, String payloadDisplayName) throws JSONException { 13 | super(payloadVersion, "com.apple.applicationaccess", payloadOrganization, payloadIdentifier, payloadDisplayName); 14 | } 15 | 16 | 17 | public void setAllowAppInstallation(boolean value) throws JSONException { 18 | getPayload().put("allowAppInstallation", value); 19 | } 20 | 21 | 22 | public void setAllowCamera(boolean value) throws JSONException { 23 | getPayload().put("allowCamera", value); 24 | } 25 | 26 | 27 | public void setAllowExplicitContent(boolean value) throws JSONException { 28 | getPayload().put("allowExplicitContent", value); 29 | } 30 | 31 | 32 | public void setAllowScreenShot(boolean value) throws JSONException { 33 | getPayload().put("allowScreenShot", value); 34 | } 35 | 36 | 37 | public void setAllowYouTube(boolean value) throws JSONException { 38 | getPayload().put("allowYouTube", value); 39 | } 40 | 41 | 42 | public void setAllowiTunes(boolean value) throws JSONException { 43 | getPayload().put("allowAppInstallation", value); 44 | } 45 | 46 | 47 | public void setAllowSafari(boolean value) throws JSONException { 48 | getPayload().put("allowSafari", value); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/management/SCEPPayload.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.management; 2 | 3 | import java.util.*; 4 | 5 | import org.json.*; 6 | 7 | /** 8 | * An MDM payload for SCEP (Simple Certificate Enrollment Protocol). 9 | * 10 | * @author Sylvain Pedneault 11 | */ 12 | public class SCEPPayload extends MobileConfigPayload { 13 | 14 | public SCEPPayload(int payloadVersion, String payloadOrganization, String payloadIdentifier, String payloadDisplayName, String url) throws JSONException { 15 | super(payloadVersion, "com.apple.encrypted-profile-service", payloadOrganization, payloadIdentifier, payloadDisplayName); 16 | JSONObject payload = getPayload(); 17 | payload.put("URL", url); 18 | } 19 | 20 | 21 | public void setName(String value) throws JSONException { 22 | getPayload().put("Name", value); 23 | } 24 | 25 | 26 | public void setSubject(String value) throws JSONException { 27 | String[] parts = value.split("/"); 28 | List list = new ArrayList(); 29 | for (String part : parts) { 30 | String[] subparts = value.split("="); 31 | list.add(subparts); 32 | } 33 | String[][] subject = list.toArray(new String[0][0]); 34 | setSubject(subject); 35 | } 36 | 37 | 38 | public void setSubject(String[][] value) throws JSONException { 39 | getPayload().put("Subject", value); 40 | } 41 | 42 | 43 | public void setChallenge(String value) throws JSONException { 44 | getPayload().put("Challenge", value); 45 | } 46 | 47 | 48 | public void setKeysize(int value) throws JSONException { 49 | getPayload().put("Keysize", value); 50 | } 51 | 52 | 53 | public void setKeyType(String value) throws JSONException { 54 | getPayload().put("Key Type", value); 55 | } 56 | 57 | 58 | public void setKeyUsage(int value) throws JSONException { 59 | getPayload().put("Key Usage", value); 60 | } 61 | 62 | 63 | public JSONObject addSubjectAltName() throws JSONException { 64 | JSONObject object = new JSONObject(); 65 | getPayload().put("SubjectAltName", object); 66 | return object; 67 | } 68 | 69 | 70 | public JSONObject addGetCACaps() throws JSONException { 71 | JSONObject object = new JSONObject(); 72 | getPayload().put("GetCACaps", object); 73 | return object; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/management/VPNPayload.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.management; 2 | 3 | import org.json.*; 4 | 5 | /** 6 | * An MDM payload for VPN. 7 | * 8 | * @author Sylvain Pedneault 9 | */ 10 | public class VPNPayload extends MobileConfigPayload { 11 | 12 | public static final String VPNTYPE_L2TP = "L2TP"; 13 | public static final String VPNTYPE_PPTP = "PPTP"; 14 | public static final String VPNTYPE_IPSec = "IPSec"; 15 | 16 | 17 | public VPNPayload(int payloadVersion, String payloadOrganization, String payloadIdentifier, String payloadDisplayName, String userDefinedName, boolean overridePrimary, String vpnType) throws JSONException { 18 | super(payloadVersion, "com.apple.vpn.managed", payloadOrganization, payloadIdentifier, payloadDisplayName); 19 | JSONObject payload = getPayload(); 20 | payload.put("UserDefinedName", userDefinedName); 21 | payload.put("OverridePrimary", overridePrimary); 22 | payload.put("VPNType", vpnType); 23 | } 24 | 25 | 26 | public JSONObject addPPP() throws JSONException { 27 | JSONObject object = new JSONObject(); 28 | getPayload().put("PPP", object); 29 | return object; 30 | } 31 | 32 | 33 | public JSONObject addIPSec() throws JSONException { 34 | JSONObject object = new JSONObject(); 35 | getPayload().put("IPSec", object); 36 | return object; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/management/WebClipPayload.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.management; 2 | 3 | import org.json.*; 4 | 5 | /** 6 | * An MDM payload for WebClip. 7 | * 8 | * @author Sylvain Pedneault 9 | */ 10 | public class WebClipPayload extends MobileConfigPayload { 11 | 12 | public WebClipPayload(int payloadVersion, String payloadOrganization, String payloadIdentifier, String payloadDisplayName, String url, String label) throws JSONException { 13 | super(payloadVersion, "com.apple.webClip.managed", payloadOrganization, payloadIdentifier, payloadDisplayName); 14 | JSONObject payload = getPayload(); 15 | payload.put("URL", url); 16 | payload.put("Label", label); 17 | } 18 | 19 | 20 | public void setIcon(Object data) throws JSONException { 21 | getPayload().put("Icon", data); 22 | } 23 | 24 | 25 | public void setIsRemovable(boolean value) throws JSONException { 26 | getPayload().put("IsRemovable", value); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/management/WiFiPayload.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.management; 2 | 3 | import org.json.*; 4 | 5 | /** 6 | * An MDM payload for Wi-Fi. 7 | * 8 | * @author Sylvain Pedneault 9 | */ 10 | public class WiFiPayload extends MobileConfigPayload { 11 | 12 | public WiFiPayload(int payloadVersion, String payloadOrganization, String payloadIdentifier, String payloadDisplayName, String SSID_STR, boolean hiddenNetwork, String encryptionType) throws JSONException { 13 | super(payloadVersion, "com.apple.wifi.managed", payloadOrganization, payloadIdentifier, payloadDisplayName); 14 | JSONObject payload = getPayload(); 15 | payload.put("SSID_STR", SSID_STR); 16 | payload.put("HIDDEN_NETWORK", hiddenNetwork); 17 | payload.put("EncryptionType", encryptionType); 18 | } 19 | 20 | 21 | public void setPassword(String value) throws JSONException { 22 | getPayload().put("Password", value); 23 | } 24 | 25 | 26 | public JSONObject addEAPClientConfiguration() throws JSONException { 27 | JSONObject object = new JSONObject(); 28 | getPayload().put("EAPClientConfiguration", object); 29 | return object; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/management/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 |

Specific payloads for Apple's MDM technology

10 | 11 |

MDM is not involved in Apple Push Notification, but uses the 12 | same communication technologies as APN.

13 |

Nevertheless, this package is provided in an effort to help 14 | javapns become more widely used.

15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | Classes for pushing notifications through Apple servers. 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/transmission/NotificationProgressListener.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.transmission; 2 | 3 | /** 4 | *

An event listener for monitoring progress of NotificationThreads

5 | * 6 | * @author Sylvain Pedneault 7 | */ 8 | public interface NotificationProgressListener { 9 | 10 | public void eventAllThreadsStarted(NotificationThreads notificationThreads); 11 | 12 | 13 | public void eventThreadStarted(NotificationThread notificationThread); 14 | 15 | 16 | public void eventThreadFinished(NotificationThread notificationThread); 17 | 18 | 19 | public void eventConnectionRestarted(NotificationThread notificationThread); 20 | 21 | 22 | public void eventAllThreadsFinished(NotificationThreads notificationThreads); 23 | 24 | 25 | public void eventCriticalException(NotificationThread notificationThread, Exception exception); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/transmission/PushQueue.java: -------------------------------------------------------------------------------- 1 | package javapns.notification.transmission; 2 | 3 | import java.util.*; 4 | 5 | import javapns.devices.*; 6 | import javapns.devices.exceptions.*; 7 | import javapns.notification.*; 8 | 9 | /** 10 | * A queue backed by an asynchronous notification thread or threads. 11 | * 12 | * @author Sylvain Pedneault 13 | */ 14 | public interface PushQueue { 15 | 16 | /** 17 | * Queue a message for delivery. A thread will pick it up and push it asynchroneously. 18 | * This method has no effect if the underlying notification thread is not in QUEUE mode. 19 | * @param payload a payload 20 | * @param token a device token 21 | * @return the actual queue to which the message was added, which could be a different one if the request was delegated to a sub-queue 22 | * @throws InvalidDeviceTokenFormatException 23 | */ 24 | public PushQueue add(Payload payload, String token) throws InvalidDeviceTokenFormatException; 25 | 26 | 27 | /** 28 | * Queue a message for delivery. A thread will pick it up and push it asynchroneously. 29 | * This method has no effect if the underlying notification thread is not in QUEUE mode. 30 | * @param payload a payload 31 | * @param device a device 32 | * @return the actual queue to which the message was added, which could be a different one if the request was delegated to a sub-queue 33 | */ 34 | public PushQueue add(Payload payload, Device device); 35 | 36 | 37 | /** 38 | * Queue a message for delivery. A thread will pick it up and push it asynchroneously. 39 | * This method has no effect if the underlying notification thread is not in QUEUE mode. 40 | * @param message a payload/device pair 41 | * @return the actual queue to which the message was added, which could be a different one if the request was delegated to a sub-queue 42 | */ 43 | public PushQueue add(PayloadPerDevice message); 44 | 45 | 46 | /** 47 | * Start the transmission thread(s) working for the queue. 48 | * @return the queue itself, as a handy shortcut to create and start a queue in a single line of code 49 | */ 50 | public PushQueue start(); 51 | 52 | 53 | /** 54 | * Get a list of critical exceptions that underlying threads experienced. 55 | * Critical exceptions include CommunicationException and KeystoreException. 56 | * Exceptions related to tokens, payloads and such are *not* included here, 57 | * as they are noted in individual PushedNotification objects. 58 | * If critical exceptions are present, the underlying thread(s) is most 59 | * likely not working at all and you should solve the problem before 60 | * trying to go any further. 61 | * 62 | * @return a list of critical exceptions 63 | */ 64 | 65 | public List getCriticalExceptions(); 66 | 67 | 68 | /** 69 | * Get a list of all notifications pushed through this queue. 70 | * 71 | * @return a list of pushed notifications 72 | */ 73 | public PushedNotifications getPushedNotifications(); 74 | 75 | 76 | /** 77 | * Clear the internal lists of PushedNotification objects maintained by this queue. 78 | * You should invoke this method once you no longer need the list of PushedNotification objects so that memory can be reclaimed. 79 | */ 80 | public void clearPushedNotifications(); 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/javapns/notification/transmission/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | Specialized classes for transmitting notifications to large number of devices. 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/javapns/overview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 |

JavaPNS 2.1

10 |

A Java library for interacting with Apple's Push Notification Service.

11 | 12 |

Usage

13 |

The simplest way of pushing notifications with JavaPNS is to use the javapns.Push class:

14 |

15 | import javapns.Push;
16 |         
17 | public class PushTest {
18 |  
19 | 	public static void main(String[] args) {
20 | 	
21 | 		Push.alert("Hello World!", "keystore.p12", "keystore_password", false, "Your token");
22 | 	}
23 | }
24 | 
25 | 26 | For more details about using the library, see the on-line wiki at: 27 | 28 | http://code.google.com/p/javapns/w/list 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/java/javapns/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 |

The JavaPNS library

10 |

See the javapns.Push class for easy push notifications.

11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/javapns/test/FeedbackTest.java: -------------------------------------------------------------------------------- 1 | package javapns.test; 2 | 3 | import java.util.*; 4 | 5 | import javapns.*; 6 | import javapns.communication.exceptions.*; 7 | import javapns.devices.*; 8 | 9 | /** 10 | * A command-line test facility for the Feedback Service. 11 | *

Example: java -cp "[required libraries]" javapns.test.FeedbackTest keystore.p12 mypass

12 | * 13 | *

By default, this test uses the sandbox service. To switch, add "production" as a third parameter:

14 | *

Example: java -cp "[required libraries]" javapns.test.FeedbackTest keystore.p12 mypass production

15 | * 16 | * @author Sylvain Pedneault 17 | */ 18 | public class FeedbackTest extends TestFoundation { 19 | 20 | /** 21 | * Execute this class from the command line to run tests. 22 | * 23 | * @param args 24 | */ 25 | public static void main(String[] args) { 26 | 27 | /* Verify that the test is being invoked */ 28 | if (!verifyCorrectUsage(FeedbackTest.class, args, "keystore-path", "keystore-password", "[production|sandbox]")) return; 29 | 30 | /* Initialize Log4j to print logs to console */ 31 | configureBasicLogging(); 32 | 33 | /* Get a list of inactive devices */ 34 | feedbackTest(args); 35 | } 36 | 37 | 38 | private FeedbackTest() { 39 | } 40 | 41 | 42 | /** 43 | * Retrieves a list of inactive devices from the Feedback service. 44 | * @param args 45 | */ 46 | private static void feedbackTest(String[] args) { 47 | String keystore = args[0]; 48 | String password = args[1]; 49 | boolean production = args.length >= 3 ? args[2].equalsIgnoreCase("production") : false; 50 | try { 51 | List devices = Push.feedback(keystore, password, production); 52 | 53 | for (Device device : devices) { 54 | System.out.println("Inactive device: " + device.getToken()); 55 | } 56 | } catch (CommunicationException e) { 57 | e.printStackTrace(); 58 | } catch (KeystoreException e) { 59 | e.printStackTrace(); 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/javapns/test/NotificationTest.java: -------------------------------------------------------------------------------- 1 | package javapns.test; 2 | 3 | import java.util.*; 4 | 5 | import javapns.*; 6 | import javapns.communication.exceptions.*; 7 | import javapns.devices.*; 8 | import javapns.devices.implementations.basic.*; 9 | import javapns.notification.*; 10 | import javapns.notification.transmission.*; 11 | 12 | import org.json.*; 13 | 14 | /** 15 | * A command-line test facility for the Push Notification Service. 16 | *

Example: java -cp "[required libraries]" javapns.test.NotificationTest keystore.p12 mypass 2ed202ac08ea9033665e853a3dc8bc4c5e78f7a6cf8d55910df230567037dcc4

17 | * 18 | *

By default, this test uses the sandbox service. To switch, add "production" as a fourth parameter:

19 | *

Example: java -cp "[required libraries]" javapns.test.NotificationTest keystore.p12 mypass 2ed202ac08ea9033665e853a3dc8bc4c5e78f7a6cf8d55910df230567037dcc4 production

20 | * 21 | *

Also by default, this test pushes a simple alert. To send a complex payload, add "complex" as a fifth parameter:

22 | *

Example: java -cp "[required libraries]" javapns.test.NotificationTest keystore.p12 mypass 2ed202ac08ea9033665e853a3dc8bc4c5e78f7a6cf8d55910df230567037dcc4 production complex

23 | * 24 | *

To send a simple payload to a large number of fake devices, add "threads" as a fifth parameter, the number of fake devices to construct, and the number of threads to use:

25 | *

Example: java -cp "[required libraries]" javapns.test.NotificationTest keystore.p12 mypass 2ed202ac08ea9033665e853a3dc8bc4c5e78f7a6cf8d55910df230567037dcc4 sandbox threads 1000 5

26 | * 27 | * @author Sylvain Pedneault 28 | */ 29 | public class NotificationTest extends TestFoundation { 30 | 31 | /** 32 | * Execute this class from the command line to run tests. 33 | * 34 | * @param args 35 | */ 36 | public static void main(String[] args) { 37 | 38 | /* Verify that the test is being invoked */ 39 | if (!verifyCorrectUsage(NotificationTest.class, args, "keystore-path", "keystore-password", "device-token", "[production|sandbox]", "[complex|simple|threads]", "[#devices]", "[#threads]")) return; 40 | 41 | /* Initialize Log4j to print logs to console */ 42 | configureBasicLogging(); 43 | 44 | /* Push an alert */ 45 | try { 46 | pushTest(args); 47 | } catch (CommunicationException e) { 48 | e.printStackTrace(); 49 | } catch (KeystoreException e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | 54 | 55 | private NotificationTest() { 56 | } 57 | 58 | 59 | /** 60 | * Push a test notification to a device, given command-line parameters. 61 | * 62 | * @param args 63 | * @throws KeystoreException 64 | * @throws CommunicationException 65 | */ 66 | private static void pushTest(String[] args) throws CommunicationException, KeystoreException { 67 | String keystore = args[0]; 68 | String password = args[1]; 69 | String token = args[2]; 70 | boolean production = args.length >= 4 ? args[3].equalsIgnoreCase("production") : false; 71 | boolean simulation = args.length >= 4 ? args[3].equalsIgnoreCase("simulation") : false; 72 | boolean complex = args.length >= 5 ? args[4].equalsIgnoreCase("complex") : false; 73 | boolean threads = args.length >= 5 ? args[4].equalsIgnoreCase("threads") : false; 74 | int threadDevices = args.length >= 6 ? Integer.parseInt(args[5]) : 100; 75 | int threadThreads = args.length >= 7 ? Integer.parseInt(args[6]) : 10; 76 | boolean simple = !complex && !threads; 77 | 78 | verifyKeystore(keystore, password, production); 79 | 80 | if (simple) { 81 | 82 | /* Push a test alert */ 83 | List notifications = Push.test(keystore, password, production, token); 84 | printPushedNotifications(notifications); 85 | 86 | } else if (complex) { 87 | 88 | /* Push a more complex payload */ 89 | List notifications = Push.payload(createComplexPayload(), keystore, password, production, token); 90 | printPushedNotifications(notifications); 91 | 92 | } else if (threads) { 93 | 94 | /* Push a Hello World! alert repetitively using NotificationThreads */ 95 | pushSimplePayloadUsingThreads(keystore, password, production, token, simulation, threadDevices, threadThreads); 96 | 97 | } 98 | } 99 | 100 | 101 | /** 102 | * Create a complex payload for test purposes. 103 | * @return 104 | */ 105 | @SuppressWarnings("unchecked") 106 | private static Payload createComplexPayload() { 107 | PushNotificationPayload complexPayload = PushNotificationPayload.complex(); 108 | try { 109 | // You can use addBody to add simple message, but we'll use 110 | // a more complex alert message so let's comment it 111 | complexPayload.addCustomAlertBody("My alert message"); 112 | complexPayload.addCustomAlertActionLocKey("Open App"); 113 | complexPayload.addCustomAlertLocKey("javapns rocks %@ %@%@"); 114 | ArrayList parameters = new ArrayList(); 115 | parameters.add("Test1"); 116 | parameters.add("Test"); 117 | parameters.add(2); 118 | complexPayload.addCustomAlertLocArgs(parameters); 119 | complexPayload.addBadge(45); 120 | complexPayload.addSound("default"); 121 | complexPayload.addCustomDictionary("acme", "foo"); 122 | complexPayload.addCustomDictionary("acme2", 42); 123 | ArrayList values = new ArrayList(); 124 | values.add("value1"); 125 | values.add(2); 126 | complexPayload.addCustomDictionary("acme3", values); 127 | } catch (JSONException e) { 128 | System.out.println("Error creating complex payload:"); 129 | e.printStackTrace(); 130 | } 131 | return complexPayload; 132 | } 133 | 134 | 135 | protected static void pushSimplePayloadUsingThreads(String keystore, String password, boolean production, String token, boolean simulation, int devices, int threads) { 136 | try { 137 | 138 | System.out.println("Creating PushNotificationManager and AppleNotificationServer"); 139 | AppleNotificationServer server = new AppleNotificationServerBasicImpl(keystore, password, production); 140 | System.out.println("Creating payload (simulation mode)"); 141 | // Payload payload = PushNotificationPayload.alert("Hello World!"); 142 | Payload payload = PushNotificationPayload.test(); 143 | 144 | System.out.println("Generating " + devices + " fake devices"); 145 | List deviceList = new ArrayList(devices); 146 | for (int i = 0; i < devices; i++) { 147 | String tokenToUse = token; 148 | if (tokenToUse == null || tokenToUse.length() != 64) { 149 | tokenToUse = "123456789012345678901234567890123456789012345678901234567" + (1000000 + i); 150 | } 151 | deviceList.add(new BasicDevice(tokenToUse)); 152 | } 153 | 154 | System.out.println("Creating " + threads + " notification threads"); 155 | NotificationThreads work = new NotificationThreads(server, simulation ? payload.asSimulationOnly() : payload, deviceList, threads); 156 | //work.setMaxNotificationsPerConnection(10000); 157 | System.out.println("Linking notification work debugging listener"); 158 | work.setListener(DEBUGGING_PROGRESS_LISTENER); 159 | 160 | System.out.println("Starting all threads..."); 161 | long timestamp1 = System.currentTimeMillis(); 162 | work.start(); 163 | System.out.println("All threads started, waiting for them..."); 164 | work.waitForAllThreads(); 165 | long timestamp2 = System.currentTimeMillis(); 166 | System.out.println("All threads finished in " + (timestamp2 - timestamp1) + " milliseconds"); 167 | 168 | printPushedNotifications(work.getPushedNotifications()); 169 | 170 | } catch (Exception e) { 171 | e.printStackTrace(); 172 | } 173 | } 174 | 175 | /** 176 | * A NotificationProgressListener you can use to debug NotificationThreads. 177 | */ 178 | public static final NotificationProgressListener DEBUGGING_PROGRESS_LISTENER = new NotificationProgressListener() { 179 | 180 | public void eventThreadStarted(NotificationThread notificationThread) { 181 | System.out.println(" [EVENT]: thread #" + notificationThread.getThreadNumber() + " started with " + notificationThread.getDevices().size() + " devices beginning at message id #" + notificationThread.getFirstMessageIdentifier()); 182 | } 183 | 184 | 185 | public void eventThreadFinished(NotificationThread thread) { 186 | System.out.println(" [EVENT]: thread #" + thread.getThreadNumber() + " finished: pushed messages #" + thread.getFirstMessageIdentifier() + " to " + thread.getLastMessageIdentifier() + " toward " + thread.getDevices().size() + " devices"); 187 | } 188 | 189 | 190 | public void eventConnectionRestarted(NotificationThread thread) { 191 | System.out.println(" [EVENT]: connection restarted in thread #" + thread.getThreadNumber() + " because it reached " + thread.getMaxNotificationsPerConnection() + " notifications per connection"); 192 | } 193 | 194 | 195 | public void eventAllThreadsStarted(NotificationThreads notificationThreads) { 196 | System.out.println(" [EVENT]: all threads started: " + notificationThreads.getThreads().size()); 197 | } 198 | 199 | 200 | public void eventAllThreadsFinished(NotificationThreads notificationThreads) { 201 | System.out.println(" [EVENT]: all threads finished: " + notificationThreads.getThreads().size()); 202 | } 203 | 204 | 205 | public void eventCriticalException(NotificationThread notificationThread, Exception exception) { 206 | System.out.println(" [EVENT]: critical exception occurred: " + exception); 207 | } 208 | }; 209 | 210 | 211 | /** 212 | * Print to the console a comprehensive report of all pushed notifications and results. 213 | * @param notifications a raw list of pushed notifications 214 | */ 215 | public static void printPushedNotifications(List notifications) { 216 | List failedNotifications = PushedNotification.findFailedNotifications(notifications); 217 | List successfulNotifications = PushedNotification.findSuccessfulNotifications(notifications); 218 | int failed = failedNotifications.size(); 219 | int successful = successfulNotifications.size(); 220 | 221 | if (successful > 0 && failed == 0) { 222 | printPushedNotifications("All notifications pushed successfully (" + successfulNotifications.size() + "):", successfulNotifications); 223 | } else if (successful == 0 && failed > 0) { 224 | printPushedNotifications("All notifications failed (" + failedNotifications.size() + "):", failedNotifications); 225 | } else if (successful == 0 && failed == 0) { 226 | System.out.println("No notifications could be sent, probably because of a critical error"); 227 | } else { 228 | printPushedNotifications("Some notifications failed (" + failedNotifications.size() + "):", failedNotifications); 229 | printPushedNotifications("Others succeeded (" + successfulNotifications.size() + "):", successfulNotifications); 230 | } 231 | } 232 | 233 | 234 | /** 235 | * Print to the console a list of pushed notifications. 236 | * @param description a title for this list of notifications 237 | * @param notifications a list of pushed notifications to print 238 | */ 239 | public static void printPushedNotifications(String description, List notifications) { 240 | System.out.println(description); 241 | for (PushedNotification notification : notifications) { 242 | try { 243 | System.out.println(" " + notification.toString()); 244 | } catch (Exception e) { 245 | e.printStackTrace(); 246 | } 247 | } 248 | } 249 | 250 | } 251 | -------------------------------------------------------------------------------- /src/main/java/javapns/test/TestFoundation.java: -------------------------------------------------------------------------------- 1 | package javapns.test; 2 | 3 | import javapns.communication.*; 4 | import javapns.notification.*; 5 | 6 | import org.apache.log4j.*; 7 | 8 | class TestFoundation { 9 | 10 | static boolean verifyCorrectUsage(Class testClass, String[] argsProvided, String... argsRequired) { 11 | if (argsProvided == null) return true; 12 | int numberOfArgsRequired = countArgumentsRequired(argsRequired); 13 | if (argsProvided.length < numberOfArgsRequired) { 14 | String message = getUsageMessage(testClass, argsRequired); 15 | System.out.println(message); 16 | return false; 17 | } 18 | return true; 19 | } 20 | 21 | 22 | private static String getUsageMessage(Class testClass, String... argsRequired) { 23 | StringBuilder message = new StringBuilder("Usage: "); 24 | message.append("java -cp \"\" "); 25 | message.append(testClass.getName()); 26 | for (String argRequired : argsRequired) { 27 | boolean optional = argRequired.startsWith("["); 28 | if (optional) { 29 | message.append(" ["); 30 | message.append(argRequired.substring(1, argRequired.length() - 1)); 31 | message.append("]"); 32 | } else { 33 | message.append(" <"); 34 | message.append(argRequired); 35 | message.append(">"); 36 | } 37 | } 38 | return message.toString(); 39 | } 40 | 41 | 42 | private static int countArgumentsRequired(String... argsRequired) { 43 | int numberOfArgsRequired = 0; 44 | for (String argRequired : argsRequired) { 45 | if (argRequired.startsWith("[")) break; 46 | numberOfArgsRequired++; 47 | } 48 | return numberOfArgsRequired; 49 | } 50 | 51 | 52 | /** 53 | * Enable Log4J with a basic default configuration (console only). 54 | */ 55 | public static void configureBasicLogging() { 56 | try { 57 | BasicConfigurator.configure(); 58 | } catch (Exception e) { 59 | } 60 | } 61 | 62 | 63 | /** 64 | * Validate a keystore reference and print the results to the console. 65 | * 66 | * @param keystoreReference a reference to or an actual keystore 67 | * @param password password for the keystore 68 | * @param production service to use 69 | */ 70 | public static void verifyKeystore(Object keystoreReference, String password, boolean production) { 71 | try { 72 | System.out.print("Validating keystore reference: "); 73 | KeystoreManager.validateKeystoreParameter(keystoreReference); 74 | System.out.println("VALID (keystore was found)"); 75 | } catch (Exception e) { 76 | e.printStackTrace(); 77 | } 78 | if (password != null) { 79 | try { 80 | System.out.print("Verifying keystore content: "); 81 | AppleNotificationServer server = new AppleNotificationServerBasicImpl(keystoreReference, password, production); 82 | KeystoreManager.verifyKeystoreContent(server, keystoreReference); 83 | System.out.println("VERIFIED (no common mistakes detected)"); 84 | } catch (Exception e) { 85 | e.printStackTrace(); 86 | } 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/javapns/test/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | Testing tools for the javapns library. 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/org/json/CDL.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /** 28 | * This provides static methods to convert comma delimited text into a 29 | * JSONArray, and to covert a JSONArray into comma delimited text. Comma 30 | * delimited text is a very popular format for data interchange. It is 31 | * understood by most database, spreadsheet, and organizer programs. 32 | *

33 | * Each row of text represents a row in a table or a data record. Each row 34 | * ends with a NEWLINE character. Each row contains one or more values. 35 | * Values are separated by commas. A value can contain any character except 36 | * for comma, unless is is wrapped in single quotes or double quotes. 37 | *

38 | * The first row usually contains the names of the columns. 39 | *

40 | * A comma delimited list can be converted into a JSONArray of JSONObjects. 41 | * The names for the elements in the JSONObjects can be taken from the names 42 | * in the first row. 43 | * @author JSON.org 44 | * @version 2009-06-18 45 | */ 46 | public class CDL { 47 | 48 | /** 49 | * Get the next value. The value can be wrapped in quotes. The value can 50 | * be empty. 51 | * @param x A JSONTokener of the source text. 52 | * @return The value string, or null if empty. 53 | * @throws JSONException if the quoted string is badly formed. 54 | */ 55 | private static String getValue(JSONTokener x) throws JSONException { 56 | char c; 57 | char q; 58 | StringBuffer sb; 59 | do { 60 | c = x.next(); 61 | } while (c == ' ' || c == '\t'); 62 | switch (c) { 63 | case 0: 64 | return null; 65 | case '"': 66 | case '\'': 67 | q = c; 68 | sb = new StringBuffer(); 69 | for (;;) { 70 | c = x.next(); 71 | if (c == q) { 72 | break; 73 | } 74 | if (c == 0 || c == '\n' || c == '\r') { 75 | throw x.syntaxError("Missing close quote '" + q + "'."); 76 | } 77 | sb.append(c); 78 | } 79 | return sb.toString(); 80 | case ',': 81 | x.back(); 82 | return ""; 83 | default: 84 | x.back(); 85 | return x.nextTo(','); 86 | } 87 | } 88 | 89 | /** 90 | * Produce a JSONArray of strings from a row of comma delimited values. 91 | * @param x A JSONTokener of the source text. 92 | * @return A JSONArray of strings. 93 | * @throws JSONException 94 | */ 95 | public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException { 96 | JSONArray ja = new JSONArray(); 97 | for (;;) { 98 | String value = getValue(x); 99 | if (value == null || (ja.length() == 0 && value.length() == 0)) { 100 | return null; 101 | } 102 | ja.put(value); 103 | for (;;) { 104 | char c = x.next(); 105 | if (c == ',') { 106 | break; 107 | } 108 | if (c != ' ') { 109 | if (c == '\n' || c == '\r' || c == 0) { 110 | return ja; 111 | } 112 | throw x.syntaxError("Bad character '" + c + "' (" + 113 | (int)c + ")."); 114 | } 115 | } 116 | } 117 | } 118 | 119 | /** 120 | * Produce a JSONObject from a row of comma delimited text, using a 121 | * parallel JSONArray of strings to provides the names of the elements. 122 | * @param names A JSONArray of names. This is commonly obtained from the 123 | * first row of a comma delimited text file using the rowToJSONArray 124 | * method. 125 | * @param x A JSONTokener of the source text. 126 | * @return A JSONObject combining the names and values. 127 | * @throws JSONException 128 | */ 129 | public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) 130 | throws JSONException { 131 | JSONArray ja = rowToJSONArray(x); 132 | return ja != null ? ja.toJSONObject(names) : null; 133 | } 134 | 135 | /** 136 | * Produce a JSONArray of JSONObjects from a comma delimited text string, 137 | * using the first row as a source of names. 138 | * @param string The comma delimited text. 139 | * @return A JSONArray of JSONObjects. 140 | * @throws JSONException 141 | */ 142 | public static JSONArray toJSONArray(String string) throws JSONException { 143 | return toJSONArray(new JSONTokener(string)); 144 | } 145 | 146 | /** 147 | * Produce a JSONArray of JSONObjects from a comma delimited text string, 148 | * using the first row as a source of names. 149 | * @param x The JSONTokener containing the comma delimited text. 150 | * @return A JSONArray of JSONObjects. 151 | * @throws JSONException 152 | */ 153 | public static JSONArray toJSONArray(JSONTokener x) throws JSONException { 154 | return toJSONArray(rowToJSONArray(x), x); 155 | } 156 | 157 | /** 158 | * Produce a JSONArray of JSONObjects from a comma delimited text string 159 | * using a supplied JSONArray as the source of element names. 160 | * @param names A JSONArray of strings. 161 | * @param string The comma delimited text. 162 | * @return A JSONArray of JSONObjects. 163 | * @throws JSONException 164 | */ 165 | public static JSONArray toJSONArray(JSONArray names, String string) 166 | throws JSONException { 167 | return toJSONArray(names, new JSONTokener(string)); 168 | } 169 | 170 | /** 171 | * Produce a JSONArray of JSONObjects from a comma delimited text string 172 | * using a supplied JSONArray as the source of element names. 173 | * @param names A JSONArray of strings. 174 | * @param x A JSONTokener of the source text. 175 | * @return A JSONArray of JSONObjects. 176 | * @throws JSONException 177 | */ 178 | public static JSONArray toJSONArray(JSONArray names, JSONTokener x) 179 | throws JSONException { 180 | if (names == null || names.length() == 0) { 181 | return null; 182 | } 183 | JSONArray ja = new JSONArray(); 184 | for (;;) { 185 | JSONObject jo = rowToJSONObject(names, x); 186 | if (jo == null) { 187 | break; 188 | } 189 | ja.put(jo); 190 | } 191 | if (ja.length() == 0) { 192 | return null; 193 | } 194 | return ja; 195 | } 196 | 197 | 198 | /** 199 | * Produce a comma delimited text row from a JSONArray. Values containing 200 | * the comma character will be quoted. Troublesome characters may be 201 | * removed. 202 | * @param ja A JSONArray of strings. 203 | * @return A string ending in NEWLINE. 204 | */ 205 | public static String rowToString(JSONArray ja) { 206 | StringBuffer sb = new StringBuffer(); 207 | for (int i = 0; i < ja.length(); i += 1) { 208 | if (i > 0) { 209 | sb.append(','); 210 | } 211 | Object o = ja.opt(i); 212 | if (o != null) { 213 | String s = o.toString(); 214 | if (s.indexOf(',') >= 0 || s.indexOf('\n') >= 0 || 215 | s.indexOf('\r') >= 0 || s.indexOf(0) >= 0 || 216 | s.charAt(0) == '"') { 217 | sb.append('"'); 218 | int length = s.length(); 219 | for (int j = 0; j < length; j += 1) { 220 | char c = s.charAt(j); 221 | if (c >= ' ' && c != '"') { 222 | sb.append(c); 223 | } 224 | } 225 | sb.append('"'); 226 | } else { 227 | sb.append(s); 228 | } 229 | } 230 | } 231 | sb.append('\n'); 232 | return sb.toString(); 233 | } 234 | 235 | /** 236 | * Produce a comma delimited text from a JSONArray of JSONObjects. The 237 | * first row will be a list of names obtained by inspecting the first 238 | * JSONObject. 239 | * @param ja A JSONArray of JSONObjects. 240 | * @return A comma delimited text. 241 | * @throws JSONException 242 | */ 243 | public static String toString(JSONArray ja) throws JSONException { 244 | JSONObject jo = ja.optJSONObject(0); 245 | if (jo != null) { 246 | JSONArray names = jo.names(); 247 | if (names != null) { 248 | return rowToString(names) + toString(names, ja); 249 | } 250 | } 251 | return null; 252 | } 253 | 254 | /** 255 | * Produce a comma delimited text from a JSONArray of JSONObjects using 256 | * a provided list of names. The list of names is not included in the 257 | * output. 258 | * @param names A JSONArray of strings. 259 | * @param ja A JSONArray of JSONObjects. 260 | * @return A comma delimited text. 261 | * @throws JSONException 262 | */ 263 | public static String toString(JSONArray names, JSONArray ja) 264 | throws JSONException { 265 | if (names == null || names.length() == 0) { 266 | return null; 267 | } 268 | StringBuffer sb = new StringBuffer(); 269 | for (int i = 0; i < ja.length(); i += 1) { 270 | JSONObject jo = ja.optJSONObject(i); 271 | if (jo != null) { 272 | sb.append(rowToString(jo.toJSONArray(names))); 273 | } 274 | } 275 | return sb.toString(); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/main/java/org/json/Cookie.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /** 28 | * Convert a web browser cookie specification to a JSONObject and back. 29 | * JSON and Cookies are both notations for name/value pairs. 30 | * @author JSON.org 31 | * @version 2008-09-18 32 | */ 33 | public class Cookie { 34 | 35 | /** 36 | * Produce a copy of a string in which the characters '+', '%', '=', ';' 37 | * and control characters are replaced with "%hh". This is a gentle form 38 | * of URL encoding, attempting to cause as little distortion to the 39 | * string as possible. The characters '=' and ';' are meta characters in 40 | * cookies. By convention, they are escaped using the URL-encoding. This is 41 | * only a convention, not a standard. Often, cookies are expected to have 42 | * encoded values. We encode '=' and ';' because we must. We encode '%' and 43 | * '+' because they are meta characters in URL encoding. 44 | * @param string The source string. 45 | * @return The escaped result. 46 | */ 47 | public static String escape(String string) { 48 | char c; 49 | String s = string.trim(); 50 | StringBuffer sb = new StringBuffer(); 51 | int len = s.length(); 52 | for (int i = 0; i < len; i += 1) { 53 | c = s.charAt(i); 54 | if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') { 55 | sb.append('%'); 56 | sb.append(Character.forDigit((char)((c >>> 4) & 0x0f), 16)); 57 | sb.append(Character.forDigit((char)(c & 0x0f), 16)); 58 | } else { 59 | sb.append(c); 60 | } 61 | } 62 | return sb.toString(); 63 | } 64 | 65 | 66 | /** 67 | * Convert a cookie specification string into a JSONObject. The string 68 | * will contain a name value pair separated by '='. The name and the value 69 | * will be unescaped, possibly converting '+' and '%' sequences. The 70 | * cookie properties may follow, separated by ';', also represented as 71 | * name=value (except the secure property, which does not have a value). 72 | * The name will be stored under the key "name", and the value will be 73 | * stored under the key "value". This method does not do checking or 74 | * validation of the parameters. It only converts the cookie string into 75 | * a JSONObject. 76 | * @param string The cookie specification string. 77 | * @return A JSONObject containing "name", "value", and possibly other 78 | * members. 79 | * @throws JSONException 80 | */ 81 | public static JSONObject toJSONObject(String string) throws JSONException { 82 | String n; 83 | JSONObject o = new JSONObject(); 84 | Object v; 85 | JSONTokener x = new JSONTokener(string); 86 | o.put("name", x.nextTo('=')); 87 | x.next('='); 88 | o.put("value", x.nextTo(';')); 89 | x.next(); 90 | while (x.more()) { 91 | n = unescape(x.nextTo("=;")); 92 | if (x.next() != '=') { 93 | if (n.equals("secure")) { 94 | v = Boolean.TRUE; 95 | } else { 96 | throw x.syntaxError("Missing '=' in cookie parameter."); 97 | } 98 | } else { 99 | v = unescape(x.nextTo(';')); 100 | x.next(); 101 | } 102 | o.put(n, v); 103 | } 104 | return o; 105 | } 106 | 107 | 108 | /** 109 | * Convert a JSONObject into a cookie specification string. The JSONObject 110 | * must contain "name" and "value" members. 111 | * If the JSONObject contains "expires", "domain", "path", or "secure" 112 | * members, they will be appended to the cookie specification string. 113 | * All other members are ignored. 114 | * @param o A JSONObject 115 | * @return A cookie specification string 116 | * @throws JSONException 117 | */ 118 | public static String toString(JSONObject o) throws JSONException { 119 | StringBuffer sb = new StringBuffer(); 120 | 121 | sb.append(escape(o.getString("name"))); 122 | sb.append("="); 123 | sb.append(escape(o.getString("value"))); 124 | if (o.has("expires")) { 125 | sb.append(";expires="); 126 | sb.append(o.getString("expires")); 127 | } 128 | if (o.has("domain")) { 129 | sb.append(";domain="); 130 | sb.append(escape(o.getString("domain"))); 131 | } 132 | if (o.has("path")) { 133 | sb.append(";path="); 134 | sb.append(escape(o.getString("path"))); 135 | } 136 | if (o.optBoolean("secure")) { 137 | sb.append(";secure"); 138 | } 139 | return sb.toString(); 140 | } 141 | 142 | /** 143 | * Convert %hh sequences to single characters, and 144 | * convert plus to space. 145 | * @param s A string that may contain 146 | * + (plus) and 147 | * %hh sequences. 148 | * @return The unescaped string. 149 | */ 150 | public static String unescape(String s) { 151 | int len = s.length(); 152 | StringBuffer b = new StringBuffer(); 153 | for (int i = 0; i < len; ++i) { 154 | char c = s.charAt(i); 155 | if (c == '+') { 156 | c = ' '; 157 | } else if (c == '%' && i + 2 < len) { 158 | int d = JSONTokener.dehexchar(s.charAt(i + 1)); 159 | int e = JSONTokener.dehexchar(s.charAt(i + 2)); 160 | if (d >= 0 && e >= 0) { 161 | c = (char)(d * 16 + e); 162 | i += 2; 163 | } 164 | } 165 | b.append(c); 166 | } 167 | return b.toString(); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/org/json/CookieList.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | import java.util.Iterator; 28 | 29 | /** 30 | * Convert a web browser cookie list string to a JSONObject and back. 31 | * @author JSON.org 32 | * @version 2008-09-18 33 | */ 34 | public class CookieList { 35 | 36 | /** 37 | * Convert a cookie list into a JSONObject. A cookie list is a sequence 38 | * of name/value pairs. The names are separated from the values by '='. 39 | * The pairs are separated by ';'. The names and the values 40 | * will be unescaped, possibly converting '+' and '%' sequences. 41 | * 42 | * To add a cookie to a cooklist, 43 | * cookielistJSONObject.put(cookieJSONObject.getString("name"), 44 | * cookieJSONObject.getString("value")); 45 | * @param string A cookie list string 46 | * @return A JSONObject 47 | * @throws JSONException 48 | */ 49 | public static JSONObject toJSONObject(String string) throws JSONException { 50 | JSONObject o = new JSONObject(); 51 | JSONTokener x = new JSONTokener(string); 52 | while (x.more()) { 53 | String name = Cookie.unescape(x.nextTo('=')); 54 | x.next('='); 55 | o.put(name, Cookie.unescape(x.nextTo(';'))); 56 | x.next(); 57 | } 58 | return o; 59 | } 60 | 61 | 62 | /** 63 | * Convert a JSONObject into a cookie list. A cookie list is a sequence 64 | * of name/value pairs. The names are separated from the values by '='. 65 | * The pairs are separated by ';'. The characters '%', '+', '=', and ';' 66 | * in the names and values are replaced by "%hh". 67 | * @param o A JSONObject 68 | * @return A cookie list string 69 | * @throws JSONException 70 | */ 71 | public static String toString(JSONObject o) throws JSONException { 72 | boolean b = false; 73 | Iterator keys = o.keys(); 74 | String s; 75 | StringBuffer sb = new StringBuffer(); 76 | while (keys.hasNext()) { 77 | s = keys.next().toString(); 78 | if (!o.isNull(s)) { 79 | if (b) { 80 | sb.append(';'); 81 | } 82 | sb.append(Cookie.escape(s)); 83 | sb.append("="); 84 | sb.append(Cookie.escape(o.getString(s))); 85 | b = true; 86 | } 87 | } 88 | return sb.toString(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/org/json/HTTP.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | import java.util.Iterator; 28 | 29 | /** 30 | * Convert an HTTP header to a JSONObject and back. 31 | * @author JSON.org 32 | * @version 2008-09-18 33 | */ 34 | public class HTTP { 35 | 36 | /** Carriage return/line feed. */ 37 | public static final String CRLF = "\r\n"; 38 | 39 | /** 40 | * Convert an HTTP header string into a JSONObject. It can be a request 41 | * header or a response header. A request header will contain 42 | *

{
 43 |      *    Method: "POST" (for example),
 44 |      *    "Request-URI": "/" (for example),
 45 |      *    "HTTP-Version": "HTTP/1.1" (for example)
 46 |      * }
47 | * A response header will contain 48 | *
{
 49 |      *    "HTTP-Version": "HTTP/1.1" (for example),
 50 |      *    "Status-Code": "200" (for example),
 51 |      *    "Reason-Phrase": "OK" (for example)
 52 |      * }
53 | * In addition, the other parameters in the header will be captured, using 54 | * the HTTP field names as JSON names, so that
 55 |      *    Date: Sun, 26 May 2002 18:06:04 GMT
 56 |      *    Cookie: Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s
 57 |      *    Cache-Control: no-cache
58 | * become 59 | *
{...
 60 |      *    Date: "Sun, 26 May 2002 18:06:04 GMT",
 61 |      *    Cookie: "Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s",
 62 |      *    "Cache-Control": "no-cache",
 63 |      * ...}
64 | * It does no further checking or conversion. It does not parse dates. 65 | * It does not do '%' transforms on URLs. 66 | * @param string An HTTP header string. 67 | * @return A JSONObject containing the elements and attributes 68 | * of the XML string. 69 | * @throws JSONException 70 | */ 71 | public static JSONObject toJSONObject(String string) throws JSONException { 72 | JSONObject o = new JSONObject(); 73 | HTTPTokener x = new HTTPTokener(string); 74 | String t; 75 | 76 | t = x.nextToken(); 77 | if (t.toUpperCase().startsWith("HTTP")) { 78 | 79 | // Response 80 | 81 | o.put("HTTP-Version", t); 82 | o.put("Status-Code", x.nextToken()); 83 | o.put("Reason-Phrase", x.nextTo('\0')); 84 | x.next(); 85 | 86 | } else { 87 | 88 | // Request 89 | 90 | o.put("Method", t); 91 | o.put("Request-URI", x.nextToken()); 92 | o.put("HTTP-Version", x.nextToken()); 93 | } 94 | 95 | // Fields 96 | 97 | while (x.more()) { 98 | String name = x.nextTo(':'); 99 | x.next(':'); 100 | o.put(name, x.nextTo('\0')); 101 | x.next(); 102 | } 103 | return o; 104 | } 105 | 106 | 107 | /** 108 | * Convert a JSONObject into an HTTP header. A request header must contain 109 | *
{
110 |      *    Method: "POST" (for example),
111 |      *    "Request-URI": "/" (for example),
112 |      *    "HTTP-Version": "HTTP/1.1" (for example)
113 |      * }
114 | * A response header must contain 115 | *
{
116 |      *    "HTTP-Version": "HTTP/1.1" (for example),
117 |      *    "Status-Code": "200" (for example),
118 |      *    "Reason-Phrase": "OK" (for example)
119 |      * }
120 | * Any other members of the JSONObject will be output as HTTP fields. 121 | * The result will end with two CRLF pairs. 122 | * @param o A JSONObject 123 | * @return An HTTP header string. 124 | * @throws JSONException if the object does not contain enough 125 | * information. 126 | */ 127 | public static String toString(JSONObject o) throws JSONException { 128 | Iterator keys = o.keys(); 129 | String s; 130 | StringBuffer sb = new StringBuffer(); 131 | if (o.has("Status-Code") && o.has("Reason-Phrase")) { 132 | sb.append(o.getString("HTTP-Version")); 133 | sb.append(' '); 134 | sb.append(o.getString("Status-Code")); 135 | sb.append(' '); 136 | sb.append(o.getString("Reason-Phrase")); 137 | } else if (o.has("Method") && o.has("Request-URI")) { 138 | sb.append(o.getString("Method")); 139 | sb.append(' '); 140 | sb.append('"'); 141 | sb.append(o.getString("Request-URI")); 142 | sb.append('"'); 143 | sb.append(' '); 144 | sb.append(o.getString("HTTP-Version")); 145 | } else { 146 | throw new JSONException("Not enough material for an HTTP header."); 147 | } 148 | sb.append(CRLF); 149 | while (keys.hasNext()) { 150 | s = keys.next().toString(); 151 | if (!s.equals("HTTP-Version") && !s.equals("Status-Code") && 152 | !s.equals("Reason-Phrase") && !s.equals("Method") && 153 | !s.equals("Request-URI") && !o.isNull(s)) { 154 | sb.append(s); 155 | sb.append(": "); 156 | sb.append(o.getString(s)); 157 | sb.append(CRLF); 158 | } 159 | } 160 | sb.append(CRLF); 161 | return sb.toString(); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/org/json/HTTPTokener.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /** 28 | * The HTTPTokener extends the JSONTokener to provide additional methods 29 | * for the parsing of HTTP headers. 30 | * @author JSON.org 31 | * @version 2008-09-18 32 | */ 33 | public class HTTPTokener extends JSONTokener { 34 | 35 | /** 36 | * Construct an HTTPTokener from a string. 37 | * @param s A source string. 38 | */ 39 | public HTTPTokener(String s) { 40 | super(s); 41 | } 42 | 43 | 44 | /** 45 | * Get the next token or string. This is used in parsing HTTP headers. 46 | * @throws JSONException 47 | * @return A String. 48 | */ 49 | public String nextToken() throws JSONException { 50 | char c; 51 | char q; 52 | StringBuffer sb = new StringBuffer(); 53 | do { 54 | c = next(); 55 | } while (Character.isWhitespace(c)); 56 | if (c == '"' || c == '\'') { 57 | q = c; 58 | for (;;) { 59 | c = next(); 60 | if (c < ' ') { 61 | throw syntaxError("Unterminated string."); 62 | } 63 | if (c == q) { 64 | return sb.toString(); 65 | } 66 | sb.append(c); 67 | } 68 | } 69 | for (;;) { 70 | if (c == 0 || Character.isWhitespace(c)) { 71 | return sb.toString(); 72 | } 73 | sb.append(c); 74 | c = next(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/json/JSONException.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /** 4 | * The JSONException is thrown by the JSON.org classes then things are amiss. 5 | * @author JSON.org 6 | * @version 2008-09-18 7 | */ 8 | @SuppressWarnings("serial") 9 | public class JSONException extends Exception { 10 | private Throwable cause; 11 | 12 | /** 13 | * Constructs a JSONException with an explanatory message. 14 | * @param message Detail about the reason for the exception. 15 | */ 16 | public JSONException(String message) { 17 | super(message); 18 | } 19 | 20 | public JSONException(Throwable t) { 21 | super(t.getMessage()); 22 | this.cause = t; 23 | } 24 | 25 | public Throwable getCause() { 26 | return this.cause; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/json/JSONNull.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | public class JSONNull implements JSONRawValue { 4 | 5 | @Override 6 | public String toString() { 7 | return "null"; 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/json/JSONRawValue.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | public interface JSONRawValue { 4 | 5 | @Override 6 | public String toString(); 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/org/json/JSONString.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | /** 3 | * The JSONString interface allows a toJSONString() 4 | * method so that a class can change the behavior of 5 | * JSONObject.toString(), JSONArray.toString(), 6 | * and JSONWriter.value(Object). The 7 | * toJSONString method will be used instead of the default behavior 8 | * of using the Object's toString() method and quoting the result. 9 | */ 10 | public interface JSONString { 11 | /** 12 | * The toJSONString method allows a class to produce its own JSON 13 | * serialization. 14 | * 15 | * @return A strictly syntactically correct JSON text. 16 | */ 17 | public String toJSONString(); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/json/JSONStringer.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2006 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | import java.io.StringWriter; 28 | 29 | /** 30 | * JSONStringer provides a quick and convenient way of producing JSON text. 31 | * The texts produced strictly conform to JSON syntax rules. No whitespace is 32 | * added, so the results are ready for transmission or storage. Each instance of 33 | * JSONStringer can produce one JSON text. 34 | *

35 | * A JSONStringer instance provides a value method for appending 36 | * values to the 37 | * text, and a key 38 | * method for adding keys before values in objects. There are array 39 | * and endArray methods that make and bound array values, and 40 | * object and endObject methods which make and bound 41 | * object values. All of these methods return the JSONWriter instance, 42 | * permitting cascade style. For example,

43 |  * myString = new JSONStringer()
44 |  *     .object()
45 |  *         .key("JSON")
46 |  *         .value("Hello, World!")
47 |  *     .endObject()
48 |  *     .toString();
which produces the string
49 |  * {"JSON":"Hello, World!"}
50 | *

51 | * The first method called must be array or object. 52 | * There are no methods for adding commas or colons. JSONStringer adds them for 53 | * you. Objects and arrays can be nested up to 20 levels deep. 54 | *

55 | * This can sometimes be easier than using a JSONObject to build a string. 56 | * @author JSON.org 57 | * @version 2008-09-18 58 | */ 59 | public class JSONStringer extends JSONWriter { 60 | /** 61 | * Make a fresh JSONStringer. It can be used to build one JSON text. 62 | */ 63 | public JSONStringer() { 64 | super(new StringWriter()); 65 | } 66 | 67 | /** 68 | * Return the JSON text. This method is used to obtain the product of the 69 | * JSONStringer instance. It will return null if there was a 70 | * problem in the construction of the JSON text (such as the calls to 71 | * array were not properly balanced with calls to 72 | * endArray). 73 | * @return The JSON text. 74 | */ 75 | public String toString() { 76 | return this.mode == 'd' ? this.writer.toString() : null; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/org/json/JSONWriter.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | import java.io.IOException; 4 | import java.io.Writer; 5 | 6 | /* 7 | Copyright (c) 2006 JSON.org 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | The Software shall be used for Good, not Evil. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | */ 29 | 30 | /** 31 | * JSONWriter provides a quick and convenient way of producing JSON text. 32 | * The texts produced strictly conform to JSON syntax rules. No whitespace is 33 | * added, so the results are ready for transmission or storage. Each instance of 34 | * JSONWriter can produce one JSON text. 35 | *

36 | * A JSONWriter instance provides a value method for appending 37 | * values to the 38 | * text, and a key 39 | * method for adding keys before values in objects. There are array 40 | * and endArray methods that make and bound array values, and 41 | * object and endObject methods which make and bound 42 | * object values. All of these methods return the JSONWriter instance, 43 | * permitting a cascade style. For example,

 44 |  * new JSONWriter(myWriter)
 45 |  *     .object()
 46 |  *         .key("JSON")
 47 |  *         .value("Hello, World!")
 48 |  *     .endObject();
which writes
 49 |  * {"JSON":"Hello, World!"}
50 | *

51 | * The first method called must be array or object. 52 | * There are no methods for adding commas or colons. JSONWriter adds them for 53 | * you. Objects and arrays can be nested up to 20 levels deep. 54 | *

55 | * This can sometimes be easier than using a JSONObject to build a string. 56 | * @author JSON.org 57 | * @version 2008-09-22 58 | */ 59 | public class JSONWriter { 60 | private static final int maxdepth = 20; 61 | 62 | /** 63 | * The comma flag determines if a comma should be output before the next 64 | * value. 65 | */ 66 | private boolean comma; 67 | 68 | /** 69 | * The current mode. Values: 70 | * 'a' (array), 71 | * 'd' (done), 72 | * 'i' (initial), 73 | * 'k' (key), 74 | * 'o' (object). 75 | */ 76 | protected char mode; 77 | 78 | /** 79 | * The object/array stack. 80 | */ 81 | private JSONObject stack[]; 82 | 83 | /** 84 | * The stack top index. A value of 0 indicates that the stack is empty. 85 | */ 86 | private int top; 87 | 88 | /** 89 | * The writer that will receive the output. 90 | */ 91 | protected Writer writer; 92 | 93 | /** 94 | * Make a fresh JSONWriter. It can be used to build one JSON text. 95 | */ 96 | public JSONWriter(Writer w) { 97 | this.comma = false; 98 | this.mode = 'i'; 99 | this.stack = new JSONObject[maxdepth]; 100 | this.top = 0; 101 | this.writer = w; 102 | } 103 | 104 | /** 105 | * Append a value. 106 | * @param s A string value. 107 | * @return this 108 | * @throws JSONException If the value is out of sequence. 109 | */ 110 | private JSONWriter append(String s) throws JSONException { 111 | if (s == null) { 112 | throw new JSONException("Null pointer"); 113 | } 114 | if (this.mode == 'o' || this.mode == 'a') { 115 | try { 116 | if (this.comma && this.mode == 'a') { 117 | this.writer.write(','); 118 | } 119 | this.writer.write(s); 120 | } catch (IOException e) { 121 | throw new JSONException(e); 122 | } 123 | if (this.mode == 'o') { 124 | this.mode = 'k'; 125 | } 126 | this.comma = true; 127 | return this; 128 | } 129 | throw new JSONException("Value out of sequence."); 130 | } 131 | 132 | /** 133 | * Begin appending a new array. All values until the balancing 134 | * endArray will be appended to this array. The 135 | * endArray method must be called to mark the array's end. 136 | * @return this 137 | * @throws JSONException If the nesting is too deep, or if the object is 138 | * started in the wrong place (for example as a key or after the end of the 139 | * outermost array or object). 140 | */ 141 | public JSONWriter array() throws JSONException { 142 | if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') { 143 | this.push(null); 144 | this.append("["); 145 | this.comma = false; 146 | return this; 147 | } 148 | throw new JSONException("Misplaced array."); 149 | } 150 | 151 | /** 152 | * End something. 153 | * @param m Mode 154 | * @param c Closing character 155 | * @return this 156 | * @throws JSONException If unbalanced. 157 | */ 158 | private JSONWriter end(char m, char c) throws JSONException { 159 | if (this.mode != m) { 160 | throw new JSONException(m == 'o' ? "Misplaced endObject." : 161 | "Misplaced endArray."); 162 | } 163 | this.pop(m); 164 | try { 165 | this.writer.write(c); 166 | } catch (IOException e) { 167 | throw new JSONException(e); 168 | } 169 | this.comma = true; 170 | return this; 171 | } 172 | 173 | /** 174 | * End an array. This method most be called to balance calls to 175 | * array. 176 | * @return this 177 | * @throws JSONException If incorrectly nested. 178 | */ 179 | public JSONWriter endArray() throws JSONException { 180 | return this.end('a', ']'); 181 | } 182 | 183 | /** 184 | * End an object. This method most be called to balance calls to 185 | * object. 186 | * @return this 187 | * @throws JSONException If incorrectly nested. 188 | */ 189 | public JSONWriter endObject() throws JSONException { 190 | return this.end('k', '}'); 191 | } 192 | 193 | /** 194 | * Append a key. The key will be associated with the next value. In an 195 | * object, every value must be preceded by a key. 196 | * @param s A key string. 197 | * @return this 198 | * @throws JSONException If the key is out of place. For example, keys 199 | * do not belong in arrays or if the key is null. 200 | */ 201 | public JSONWriter key(String s) throws JSONException { 202 | if (s == null) { 203 | throw new JSONException("Null key."); 204 | } 205 | if (this.mode == 'k') { 206 | try { 207 | stack[top - 1].putOnce(s, Boolean.TRUE); 208 | if (this.comma) { 209 | this.writer.write(','); 210 | } 211 | this.writer.write(JSONObject.quote(s)); 212 | this.writer.write(':'); 213 | this.comma = false; 214 | this.mode = 'o'; 215 | return this; 216 | } catch (IOException e) { 217 | throw new JSONException(e); 218 | } 219 | } 220 | throw new JSONException("Misplaced key."); 221 | } 222 | 223 | 224 | /** 225 | * Begin appending a new object. All keys and values until the balancing 226 | * endObject will be appended to this object. The 227 | * endObject method must be called to mark the object's end. 228 | * @return this 229 | * @throws JSONException If the nesting is too deep, or if the object is 230 | * started in the wrong place (for example as a key or after the end of the 231 | * outermost array or object). 232 | */ 233 | public JSONWriter object() throws JSONException { 234 | if (this.mode == 'i') { 235 | this.mode = 'o'; 236 | } 237 | if (this.mode == 'o' || this.mode == 'a') { 238 | this.append("{"); 239 | this.push(new JSONObject()); 240 | this.comma = false; 241 | return this; 242 | } 243 | throw new JSONException("Misplaced object."); 244 | 245 | } 246 | 247 | 248 | /** 249 | * Pop an array or object scope. 250 | * @param c The scope to close. 251 | * @throws JSONException If nesting is wrong. 252 | */ 253 | private void pop(char c) throws JSONException { 254 | if (this.top <= 0) { 255 | throw new JSONException("Nesting error."); 256 | } 257 | char m = this.stack[this.top - 1] == null ? 'a' : 'k'; 258 | if (m != c) { 259 | throw new JSONException("Nesting error."); 260 | } 261 | this.top -= 1; 262 | this.mode = this.top == 0 ? 'd' : this.stack[this.top - 1] == null ? 'a' : 'k'; 263 | } 264 | 265 | /** 266 | * Push an array or object scope. 267 | * @param c The scope to open. 268 | * @throws JSONException If nesting is too deep. 269 | */ 270 | private void push(JSONObject jo) throws JSONException { 271 | if (this.top >= maxdepth) { 272 | throw new JSONException("Nesting too deep."); 273 | } 274 | this.stack[this.top] = jo; 275 | this.mode = jo == null ? 'a' : 'k'; 276 | this.top += 1; 277 | } 278 | 279 | 280 | /** 281 | * Append either the value true or the value 282 | * false. 283 | * @param b A boolean. 284 | * @return this 285 | * @throws JSONException 286 | */ 287 | public JSONWriter value(boolean b) throws JSONException { 288 | return this.append(b ? "true" : "false"); 289 | } 290 | 291 | /** 292 | * Append a double value. 293 | * @param d A double. 294 | * @return this 295 | * @throws JSONException If the number is not finite. 296 | */ 297 | public JSONWriter value(double d) throws JSONException { 298 | return this.value(new Double(d)); 299 | } 300 | 301 | /** 302 | * Append a long value. 303 | * @param l A long. 304 | * @return this 305 | * @throws JSONException 306 | */ 307 | public JSONWriter value(long l) throws JSONException { 308 | return this.append(Long.toString(l)); 309 | } 310 | 311 | 312 | /** 313 | * Append an object value. 314 | * @param o The object to append. It can be null, or a Boolean, Number, 315 | * String, JSONObject, or JSONArray, or an object with a toJSONString() 316 | * method. 317 | * @return this 318 | * @throws JSONException If the value is out of sequence. 319 | */ 320 | public JSONWriter value(Object o) throws JSONException { 321 | return this.append(JSONObject.valueToString(o)); 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /src/main/java/org/json/XMLTokener.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /** 28 | * The XMLTokener extends the JSONTokener to provide additional methods 29 | * for the parsing of XML texts. 30 | * @author JSON.org 31 | * @version 2008-09-18 32 | */ 33 | @SuppressWarnings("unchecked") 34 | public class XMLTokener extends JSONTokener { 35 | 36 | 37 | /** The table of entity values. It initially contains Character values for 38 | * amp, apos, gt, lt, quot. 39 | */ 40 | public static final java.util.HashMap entity; 41 | 42 | static { 43 | entity = new java.util.HashMap(8); 44 | entity.put("amp", XML.AMP); 45 | entity.put("apos", XML.APOS); 46 | entity.put("gt", XML.GT); 47 | entity.put("lt", XML.LT); 48 | entity.put("quot", XML.QUOT); 49 | } 50 | 51 | /** 52 | * Construct an XMLTokener from a string. 53 | * @param s A source string. 54 | */ 55 | public XMLTokener(String s) { 56 | super(s); 57 | } 58 | 59 | /** 60 | * Get the text in the CDATA block. 61 | * @return The string up to the ]]>. 62 | * @throws JSONException If the ]]> is not found. 63 | */ 64 | public String nextCDATA() throws JSONException { 65 | char c; 66 | int i; 67 | StringBuffer sb = new StringBuffer(); 68 | for (;;) { 69 | c = next(); 70 | if (c == 0) { 71 | throw syntaxError("Unclosed CDATA"); 72 | } 73 | sb.append(c); 74 | i = sb.length() - 3; 75 | if (i >= 0 && sb.charAt(i) == ']' && 76 | sb.charAt(i + 1) == ']' && sb.charAt(i + 2) == '>') { 77 | sb.setLength(i); 78 | return sb.toString(); 79 | } 80 | } 81 | } 82 | 83 | 84 | /** 85 | * Get the next XML outer token, trimming whitespace. There are two kinds 86 | * of tokens: the '<' character which begins a markup tag, and the content 87 | * text between markup tags. 88 | * 89 | * @return A string, or a '<' Character, or null if there is no more 90 | * source text. 91 | * @throws JSONException 92 | */ 93 | public Object nextContent() throws JSONException { 94 | char c; 95 | StringBuffer sb; 96 | do { 97 | c = next(); 98 | } while (Character.isWhitespace(c)); 99 | if (c == 0) { 100 | return null; 101 | } 102 | if (c == '<') { 103 | return XML.LT; 104 | } 105 | sb = new StringBuffer(); 106 | for (;;) { 107 | if (c == '<' || c == 0) { 108 | back(); 109 | return sb.toString().trim(); 110 | } 111 | if (c == '&') { 112 | sb.append(nextEntity(c)); 113 | } else { 114 | sb.append(c); 115 | } 116 | c = next(); 117 | } 118 | } 119 | 120 | 121 | /** 122 | * Return the next entity. These entities are translated to Characters: 123 | * & ' > < ". 124 | * @param a An ampersand character. 125 | * @return A Character or an entity String if the entity is not recognized. 126 | * @throws JSONException If missing ';' in XML entity. 127 | */ 128 | public Object nextEntity(char a) throws JSONException { 129 | StringBuffer sb = new StringBuffer(); 130 | for (;;) { 131 | char c = next(); 132 | if (Character.isLetterOrDigit(c) || c == '#') { 133 | sb.append(Character.toLowerCase(c)); 134 | } else if (c == ';') { 135 | break; 136 | } else { 137 | throw syntaxError("Missing ';' in XML entity: &" + sb); 138 | } 139 | } 140 | String s = sb.toString(); 141 | Object e = entity.get(s); 142 | return e != null ? e : a + s + ";"; 143 | } 144 | 145 | 146 | /** 147 | * Returns the next XML meta token. This is used for skipping over 148 | * and structures. 149 | * @return Syntax characters (< > / = ! ?) are returned as 150 | * Character, and strings and names are returned as Boolean. We don't care 151 | * what the values actually are. 152 | * @throws JSONException If a string is not properly closed or if the XML 153 | * is badly structured. 154 | */ 155 | public Object nextMeta() throws JSONException { 156 | char c; 157 | char q; 158 | do { 159 | c = next(); 160 | } while (Character.isWhitespace(c)); 161 | switch (c) { 162 | case 0: 163 | throw syntaxError("Misshaped meta tag"); 164 | case '<': 165 | return XML.LT; 166 | case '>': 167 | return XML.GT; 168 | case '/': 169 | return XML.SLASH; 170 | case '=': 171 | return XML.EQ; 172 | case '!': 173 | return XML.BANG; 174 | case '?': 175 | return XML.QUEST; 176 | case '"': 177 | case '\'': 178 | q = c; 179 | for (;;) { 180 | c = next(); 181 | if (c == 0) { 182 | throw syntaxError("Unterminated string"); 183 | } 184 | if (c == q) { 185 | return Boolean.TRUE; 186 | } 187 | } 188 | default: 189 | for (;;) { 190 | c = next(); 191 | if (Character.isWhitespace(c)) { 192 | return Boolean.TRUE; 193 | } 194 | switch (c) { 195 | case 0: 196 | case '<': 197 | case '>': 198 | case '/': 199 | case '=': 200 | case '!': 201 | case '?': 202 | case '"': 203 | case '\'': 204 | back(); 205 | return Boolean.TRUE; 206 | } 207 | } 208 | } 209 | } 210 | 211 | 212 | /** 213 | * Get the next XML Token. These tokens are found inside of angle 214 | * brackets. It may be one of these characters: / > = ! ? or it 215 | * may be a string wrapped in single quotes or double quotes, or it may be a 216 | * name. 217 | * @return a String or a Character. 218 | * @throws JSONException If the XML is not well formed. 219 | */ 220 | public Object nextToken() throws JSONException { 221 | char c; 222 | char q; 223 | StringBuffer sb; 224 | do { 225 | c = next(); 226 | } while (Character.isWhitespace(c)); 227 | switch (c) { 228 | case 0: 229 | throw syntaxError("Misshaped element"); 230 | case '<': 231 | throw syntaxError("Misplaced '<'"); 232 | case '>': 233 | return XML.GT; 234 | case '/': 235 | return XML.SLASH; 236 | case '=': 237 | return XML.EQ; 238 | case '!': 239 | return XML.BANG; 240 | case '?': 241 | return XML.QUEST; 242 | 243 | // Quoted string 244 | 245 | case '"': 246 | case '\'': 247 | q = c; 248 | sb = new StringBuffer(); 249 | for (;;) { 250 | c = next(); 251 | if (c == 0) { 252 | throw syntaxError("Unterminated string"); 253 | } 254 | if (c == q) { 255 | return sb.toString(); 256 | } 257 | if (c == '&') { 258 | sb.append(nextEntity(c)); 259 | } else { 260 | sb.append(c); 261 | } 262 | } 263 | default: 264 | 265 | // Name 266 | 267 | sb = new StringBuffer(); 268 | for (;;) { 269 | sb.append(c); 270 | c = next(); 271 | if (Character.isWhitespace(c)) { 272 | return sb.toString(); 273 | } 274 | switch (c) { 275 | case 0: 276 | return sb.toString(); 277 | case '>': 278 | case '/': 279 | case '=': 280 | case '!': 281 | case '?': 282 | case '[': 283 | case ']': 284 | back(); 285 | return sb.toString(); 286 | case '<': 287 | case '"': 288 | case '\'': 289 | throw syntaxError("Bad character in a name"); 290 | } 291 | } 292 | } 293 | } 294 | 295 | 296 | /** 297 | * Skip characters until past the requested string. 298 | * If it is not found, we are left at the end of the source with a result of false. 299 | * @param to A string to skip past. 300 | * @throws JSONException 301 | */ 302 | public boolean skipPast(String to) throws JSONException { 303 | boolean b; 304 | char c; 305 | int i; 306 | int j; 307 | int offset = 0; 308 | int n = to.length(); 309 | char[] circle = new char[n]; 310 | 311 | /* 312 | * First fill the circle buffer with as many characters as are in the 313 | * to string. If we reach an early end, bail. 314 | */ 315 | 316 | for (i = 0; i < n; i += 1) { 317 | c = next(); 318 | if (c == 0) { 319 | return false; 320 | } 321 | circle[i] = c; 322 | } 323 | /* 324 | * We will loop, possibly for all of the remaining characters. 325 | */ 326 | for (;;) { 327 | j = offset; 328 | b = true; 329 | /* 330 | * Compare the circle buffer with the to string. 331 | */ 332 | for (i = 0; i < n; i += 1) { 333 | if (circle[j] != to.charAt(i)) { 334 | b = false; 335 | break; 336 | } 337 | j += 1; 338 | if (j >= n) { 339 | j -= n; 340 | } 341 | } 342 | /* 343 | * If we exit the loop with b intact, then victory is ours. 344 | */ 345 | if (b) { 346 | return true; 347 | } 348 | /* 349 | * Get the next character. If there isn't one, then defeat is ours. 350 | */ 351 | c = next(); 352 | if (c == 0) { 353 | return false; 354 | } 355 | /* 356 | * Shove the character in the circle buffer and advance the 357 | * circle offset. The offset is mod n. 358 | */ 359 | circle[offset] = c; 360 | offset += 1; 361 | if (offset >= n) { 362 | offset -= n; 363 | } 364 | } 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /src/main/java/org/json/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | A built-in implementation of the JSON.org library. 10 | 11 | 12 | --------------------------------------------------------------------------------