Package com.notnoop.apns

Source Code of com.notnoop.apns.ApnsServiceBuilder

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

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;

import java.security.KeyStore;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;

import com.notnoop.apns.internal.*;
import com.notnoop.exceptions.InvalidSSLConfig;
import com.notnoop.exceptions.RuntimeIOException;

import static com.notnoop.apns.internal.Utilities.*;

/**
* The class is used to create instances of {@link ApnsService}.
*
* Note that this class is not synchronized.  If multiple threads access a
* {@code ApnsServiceBuilder} instance concurrently, and at least on of the
* threads modifies one of the attributes structurally, it must be
* synchronized externally.
*
* Starting a new {@code ApnsService} is easy:
*
* <pre>
*   ApnsService = APNS.newService()
*    .withCert("/path/to/certificate.p12", "MyCertPassword")
*    .withSandboxDestination()
*    .build()
* </pre>
*/
public class ApnsServiceBuilder {
    private static final String KEYSTORE_TYPE = "PKCS12";
    private static final String KEY_ALGORITHM = ((java.security.Security.getProperty("ssl.KeyManagerFactory.algorithm") == null)? "sunx509" : java.security.Security.getProperty("ssl.KeyManagerFactory.algorithm"));

    private SSLContext sslContext;

    private int readTimeout = 0;
    private int connectTimeout = 0;

    private String gatewayHost;
    private int gatewayPort = -1;

    private String feedbackHost;
    private int feedbackPort;
    private int pooledMax = 1;
    private int cacheLength = ApnsConnection.DEFAULT_CACHE_LENGTH;
    private boolean autoAdjustCacheLength = true;
    private ExecutorService executor = null;

    private ReconnectPolicy reconnectPolicy = ReconnectPolicy.Provided.EVERY_HALF_HOUR.newObject();
    private boolean isQueued = false;
    private ThreadFactory queueThreadFactory = null;
   
    private boolean isBatched = false;
    private int batchWaitTimeInSec;
    private int batchMaxWaitTimeInSec;
    private ThreadFactory batchThreadFactory = null;
   
    private ApnsDelegate delegate = ApnsDelegate.EMPTY;
    private Proxy proxy = null;
    private String proxyUsername = null;
    private String proxyPassword = null;
    private boolean errorDetection = true;
    private ThreadFactory errorDetectionThreadFactory = null;

    /**
     * Constructs a new instance of {@code ApnsServiceBuilder}
     */
    public ApnsServiceBuilder() { sslContext = null; }

    /**
     * Specify the certificate used to connect to Apple APNS
     * servers.  This relies on the path (absolute or relative to
     * working path) to the keystore (*.p12) containing the
     * certificate, along with the given password.
     *
     * The keystore needs to be of PKCS12 and the keystore
     * needs to be encrypted using the SunX509 algorithm.  Both
     * of these settings are the default.
     *
     * This library does not support password-less p12 certificates, due to a
     * Oracle Java library <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6415637">
     * Bug 6415637</a>.  There are three workarounds: use a password-protected
     * certificate, use a different boot Java SDK implementation, or construct
     * the `SSLContext` yourself!  Needless to say, the password-protected
     * certificate is most recommended option.
     *
     * @param fileName  the path to the certificate
     * @param password  the password of the keystore
     * @return  this
     * @throws RuntimeIOException if it {@code fileName} cannot be
     *          found or read
     * @throws InvalidSSLConfig if fileName is invalid Keystore
     *  or the password is invalid
     */
    public ApnsServiceBuilder withCert(String fileName, String password)
    throws RuntimeIOException, InvalidSSLConfig {
        FileInputStream stream = null;
        try {
            stream = new FileInputStream(fileName);
            return withCert(stream, password);
        } catch (FileNotFoundException e) {
            throw new RuntimeIOException(e);
        } finally {
            Utilities.close(stream);
        }
    }

    /**
     * Specify the certificate used to connect to Apple APNS
     * servers.  This relies on the stream of keystore (*.p12)
     * containing the certificate, along with the given password.
     *
     * The keystore needs to be of PKCS12 and the keystore
     * needs to be encrypted using the SunX509 algorithm.  Both
     * of these settings are the default.
     *
     * This library does not support password-less p12 certificates, due to a
     * Oracle Java library <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6415637">
     * Bug 6415637</a>.  There are three workarounds: use a password-protected
     * certificate, use a different boot Java SDK implementation, or constract
     * the `SSLContext` yourself!  Needless to say, the password-protected
     * certificate is most recommended option.
     *
     * @param stream    the keystore represented as input stream
     * @param password  the password of the keystore
     * @return  this
     * @throws InvalidSSLConfig if stream is invalid Keystore
     *  or the password is invalid
     */
    public ApnsServiceBuilder withCert(InputStream stream, String password)
    throws InvalidSSLConfig {
        assertPasswordNotEmpty(password);
        return withSSLContext(
                newSSLContext(stream, password,
                        KEYSTORE_TYPE, KEY_ALGORITHM));
    }

    /**
     * Specify the certificate used to connect to Apple APNS
     * servers.  This relies on a keystore (*.p12)
     * containing the certificate, along with the given password.
     *
     * This library does not support password-less p12 certificates, due to a
     * Oracle Java library <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6415637">
     * Bug 6415637</a>.  There are three workarounds: use a password-protected
     * certificate, use a different boot Java SDK implementation, or construct
     * the `SSLContext` yourself!  Needless to say, the password-protected
     * certificate is most recommended option.
     *
     * @param keyStore  the keystore
     * @param password  the password of the keystore
     * @return  this
     * @throws InvalidSSLConfig if stream is invalid Keystore
     *  or the password is invalid
     */
    public ApnsServiceBuilder withCert(KeyStore keyStore, String password)
    throws InvalidSSLConfig {
        assertPasswordNotEmpty(password);
        return withSSLContext(
                newSSLContext(keyStore, password, KEY_ALGORITHM));
    }
   
  private void assertPasswordNotEmpty(String password) {
    if (password == null || password.length() == 0) {
            throw new IllegalArgumentException("Passwords must be specified." +
                    "Oracle Java SDK does not support passwordless p12 certificates");
        }
  }
   
    /**
     * Specify the SSLContext that should be used to initiate the
     * connection to Apple Server.
     *
     * Most clients would use {@link #withCert(InputStream, String)}
     * or {@link #withCert(String, String)} instead.  But some
     * clients may need to represent the Keystore in a different
     * format than supported.
     *
     * @param sslContext    Context to be used to create secure connections
     * @return  this
     */
    public ApnsServiceBuilder withSSLContext(SSLContext sslContext) {
        this.sslContext = sslContext;
        return this;
    }
   
    /**
     * Specify the timeout value to be set in new setSoTimeout in created
     * sockets, for both feedback and push connections, in milliseconds.
     * @param readTimeout timeout value to be set in new setSoTimeout
     * @return this
     */
    public ApnsServiceBuilder withReadTimeout(int readTimeout) {
      this.readTimeout = readTimeout;
      return this;
    }

    /**
     * Specify the timeout value to use for connectionTimeout in created
     * sockets, for both feedback and push connections, in milliseconds.
     * @param connectTimeout timeout value to use for connectionTimeout
     * @return this
     */
    public ApnsServiceBuilder withConnectTimeout(int connectTimeout) {
      this.connectTimeout = connectTimeout;
      return this;
    }

    /**
     * Specify the gateway server for sending Apple iPhone
     * notifications.
     *
     * Most clients should use {@link #withSandboxDestination()}
     * or {@link #withProductionDestination()}.  Clients may use
     * this method to connect to mocking tests and such.
     *
     * @param host  hostname the notification gateway of Apple
     * @param port  port of the notification gateway of Apple
     * @return  this
     */
    public ApnsServiceBuilder withGatewayDestination(String host, int port) {
        this.gatewayHost = host;
        this.gatewayPort = port;
        return this;
    }

    /**
     * Specify the Feedback for getting failed devices from
     * Apple iPhone Push servers.
     *
     * Most clients should use {@link #withSandboxDestination()}
     * or {@link #withProductionDestination()}.  Clients may use
     * this method to connect to mocking tests and such.
     *
     * @param host  hostname of the feedback server of Apple
     * @param port  port of the feedback server of Apple
     * @return this
     */
    public ApnsServiceBuilder withFeedbackDestination(String host, int port) {
        this.feedbackHost = host;
        this.feedbackPort = port;
        return this;
    }

    /**
     * Specify to use Apple servers as iPhone gateway and feedback servers.
     *
     * If the passed {@code isProduction} is true, then it connects to the
     * production servers, otherwise, it connects to the sandbox servers
     *
     * @param isProduction  determines which Apple servers should be used:
     *               production or sandbox
     * @return this
     */
    public ApnsServiceBuilder withAppleDestination(boolean isProduction) {
        if (isProduction) {
            return withProductionDestination();
        } else {
            return withSandboxDestination();
        }
    }

    /**
     * Specify to use the Apple sandbox servers as iPhone gateway
     * and feedback servers.
     *
     * This is desired when in testing and pushing notifications
     * with a development provision.
     *
     * @return  this
     */
    public ApnsServiceBuilder withSandboxDestination() {
        return withGatewayDestination(SANDBOX_GATEWAY_HOST, SANDBOX_GATEWAY_PORT)
        .withFeedbackDestination(SANDBOX_FEEDBACK_HOST, SANDBOX_FEEDBACK_PORT);
    }

    /**
     * Specify to use the Apple Production servers as iPhone gateway
     * and feedback servers.
     *
     * This is desired when sending notifications to devices with
     * a production provision (whether through App Store or Ad hoc
     * distribution).
     *
     * @return  this
     */
    public ApnsServiceBuilder withProductionDestination() {
        return withGatewayDestination(PRODUCTION_GATEWAY_HOST, PRODUCTION_GATEWAY_PORT)
        .withFeedbackDestination(PRODUCTION_FEEDBACK_HOST, PRODUCTION_FEEDBACK_PORT);
    }

    /**
     * Specify the reconnection policy for the socket connection.
     *
     * Note: This option has no effect when using non-blocking
     * connections.
     */
    public ApnsServiceBuilder withReconnectPolicy(ReconnectPolicy rp) {
        this.reconnectPolicy = rp;
        return this;
    }
   
    /**
     * Specify if the notification cache should auto adjust.
     * Default is true
     *
     * @param autoAdjustCacheLength the notification cache should auto adjust.
     * @return this
     */
    public ApnsServiceBuilder withAutoAdjustCacheLength(boolean autoAdjustCacheLength) {
        this.autoAdjustCacheLength = autoAdjustCacheLength;
        return this;
    }

    /**
     * Specify the reconnection policy for the socket connection.
     *
     * Note: This option has no effect when using non-blocking
     * connections.
     */
    public ApnsServiceBuilder withReconnectPolicy(ReconnectPolicy.Provided rp) {
        this.reconnectPolicy = rp.newObject();
        return this;
    }

    /**
     * Specify the address of the SOCKS proxy the connection should
     * use.
     *
     * <p>Read the <a href="http://java.sun.com/javase/6/docs/technotes/guides/net/proxies.html">
     * Java Networking and Proxies</a> guide to understand the
     * proxies complexity.
     *
     * <p>Be aware that this method only handles SOCKS proxies, not
     * HTTPS proxies.  Use {@link #withProxy(Proxy)} instead.
     *
     * @param host  the hostname of the SOCKS proxy
     * @param port  the port of the SOCKS proxy server
     * @return  this
     */
    public ApnsServiceBuilder withSocksProxy(String host, int port) {
        Proxy proxy = new Proxy(Proxy.Type.SOCKS,
                new InetSocketAddress(host, port));
        return withProxy(proxy);
    }

    /**
     * Specify the proxy and the authentication parameters to be used
     * to establish the connections to Apple Servers.
     *
     * <p>Read the <a href="http://java.sun.com/javase/6/docs/technotes/guides/net/proxies.html">
     * Java Networking and Proxies</a> guide to understand the
     * proxies complexity.
     *
     * @param proxy the proxy object to be used to create connections
     * @param proxyUsername a String object representing the username of the proxy server
     * @param proxyPassword a String object representing the password of the proxy server
     * @return  this
     */
    public ApnsServiceBuilder withAuthProxy(Proxy proxy, String proxyUsername, String proxyPassword) {
        this.proxy = proxy;
        this.proxyUsername = proxyUsername;
        this.proxyPassword = proxyPassword;
        return this;
    }
   
    /**
     * Specify the proxy to be used to establish the connections
     * to Apple Servers
     *
     * <p>Read the <a href="http://java.sun.com/javase/6/docs/technotes/guides/net/proxies.html">
     * Java Networking and Proxies</a> guide to understand the
     * proxies complexity.
     *
     * @param proxy the proxy object to be used to create connections
     * @return  this
     */
    public ApnsServiceBuilder withProxy(Proxy proxy) {
        this.proxy = proxy;
        return this;
    }
   
    /**
     * Specify the number of notifications to cache for error purposes.
     * Default is 100
     *
     * @param cacheLength  Number of notifications to cache for error purposes
     * @return  this
     */
    public ApnsServiceBuilder withCacheLength(int cacheLength) {
        this.cacheLength = cacheLength;
        return this;
    }

    /**
     * Specify the socket to be used as underlying socket to connect
     * to the APN service.
     *
     * This assumes that the socket connects to a SOCKS proxy.
     *
     * @deprecated use {@link ApnsServiceBuilder#withProxy(Proxy)} instead
     * @param proxySocket   the underlying socket for connections
     * @return  this
     */
    @Deprecated
    public ApnsServiceBuilder withProxySocket(Socket proxySocket) {
        return this.withProxy(new Proxy(Proxy.Type.SOCKS,
                proxySocket.getRemoteSocketAddress()));
    }

    /**
     * Constructs a pool of connections to the notification servers.
     *
     * Apple servers recommend using a pooled connection up to
     * 15 concurrent persistent connections to the gateways.
     *
     * Note: This option has no effect when using non-blocking
     * connections.
     */
    public ApnsServiceBuilder asPool(int maxConnections) {
        return asPool(Executors.newFixedThreadPool(maxConnections), maxConnections);
    }

    /**
     * Constructs a pool of connections to the notification servers.
     *
     * Apple servers recommend using a pooled connection up to
     * 15 concurrent persistent connections to the gateways.
     *
     * Note: This option has no effect when using non-blocking
     * connections.
     *
     * Note: The maxConnections here is used as a hint to how many connections
     * get created.
     */
    public ApnsServiceBuilder asPool(ExecutorService executor, int maxConnections) {
        this.pooledMax = maxConnections;
        this.executor = executor;
        return this;
    }

    /**
     * Constructs a new thread with a processing queue to process
     * notification requests.
     *
     * @return  this
     */
    public ApnsServiceBuilder asQueued() {
        return asQueued(Executors.defaultThreadFactory());
    }
   
    /**
     * Constructs a new thread with a processing queue to process
     * notification requests.
     *
     * @param threadFactory
     *            thread factory to use for queue processing
     * @return  this
     */
    public ApnsServiceBuilder asQueued(ThreadFactory threadFactory) {
        this.isQueued = true;
        this.queueThreadFactory = threadFactory;
        return this;
    }
   
    /**
     * Construct service which will process notification requests in batch.
     * After each request batch will wait <code>waitTimeInSec (set as 5sec)</code> for more request to come
     * before executing but not more than <code>maxWaitTimeInSec (set as 10sec)</code>
     *
     * Note: It is not recommended to use pooled connection
     */
    public ApnsServiceBuilder asBatched() {
        return asBatched(5, 10);
    }
   
    /**
     * Construct service which will process notification requests in batch.
     * After each request batch will wait <code>waitTimeInSec</code> for more request to come
     * before executing but not more than <code>maxWaitTimeInSec</code>
     *
     * Note: It is not recommended to use pooled connection
     *
     * @param waitTimeInSec
     *            time to wait for more notification request before executing
     *            batch
     * @param maxWaitTimeInSec
     *            maximum wait time for batch before executing
     */
    public ApnsServiceBuilder asBatched(int waitTimeInSec, int maxWaitTimeInSec) {
        return asBatched(waitTimeInSec, maxWaitTimeInSec, null);
    }
   
    /**
     * Construct service which will process notification requests in batch.
     * After each request batch will wait <code>waitTimeInSec</code> for more request to come
     * before executing but not more than <code>maxWaitTimeInSec</code>
     *
     * Each batch creates new connection and close it after finished.
     * In case reconnect policy is specified it will be applied by batch processing.
     * E.g.: {@link ReconnectPolicy.Provided#EVERY_HALF_HOUR} will reconnect the connection in case batch is running for more than half an hour
     *
     * Note: It is not recommended to use pooled connection
     *
     * @param waitTimeInSec
     *            time to wait for more notification request before executing
     *            batch
     * @param maxWaitTimeInSec
     *            maximum wait time for batch before executing
     * @param threadFactory
     *            thread factory to use for batch processing
     */
    public ApnsServiceBuilder asBatched(int waitTimeInSec, int maxWaitTimeInSec, ThreadFactory threadFactory) {
        this.isBatched = true;
        this.batchWaitTimeInSec = waitTimeInSec;
        this.batchMaxWaitTimeInSec = maxWaitTimeInSec;
        this.batchThreadFactory = threadFactory;
        return this;
    }
   
   
    /**
     * Sets the delegate of the service, that gets notified of the
     * status of message delivery.
     *
     * Note: This option has no effect when using non-blocking
     * connections.
     */
    public ApnsServiceBuilder withDelegate(ApnsDelegate delegate) {
        this.delegate = delegate == null ? ApnsDelegate.EMPTY : delegate;
        return this;
    }

    /**
     * Disables the enhanced error detection, enabled by the
     * enhanced push notification interface.  Error detection is
     * enabled by default.
     *
     * This setting is desired when the application shouldn't spawn
     * new threads.
     *
     * @return  this
     */
    public ApnsServiceBuilder withNoErrorDetection() {
        this.errorDetection = false;
        return this;
    }

    /**
     * Provide a custom source for threads used for monitoring connections.
     *
     * This setting is desired when the application must obtain threads from a
     * controlled environment Google App Engine.
     * @param threadFactory
     *            thread factory to use for error detection
     * @return  this
     */
    public ApnsServiceBuilder withErrorDetectionThreadFactory(ThreadFactory threadFactory) {
        this.errorDetectionThreadFactory = threadFactory;
        return this;
    }

    /**
     * Returns a fully initialized instance of {@link ApnsService},
     * according to the requested settings.
     *
     * @return  a new instance of ApnsService
     */
    public ApnsService build() {
        checkInitialization();
        ApnsService service;

        SSLSocketFactory sslFactory = sslContext.getSocketFactory();
        ApnsFeedbackConnection feedback = new ApnsFeedbackConnection(sslFactory, feedbackHost, feedbackPort, proxy, readTimeout, connectTimeout, proxyUsername, proxyPassword);

        ApnsConnection conn = new ApnsConnectionImpl(sslFactory, gatewayHost,
            gatewayPort, proxy, proxyUsername, proxyPassword, reconnectPolicy,
                delegate, errorDetection, errorDetectionThreadFactory, cacheLength,
                autoAdjustCacheLength, readTimeout, connectTimeout);
        if (pooledMax != 1) {
            conn = new ApnsPooledConnection(conn, pooledMax, executor);
        }

        service = new ApnsServiceImpl(conn, feedback);

        if (isQueued) {
            service = new QueuedApnsService(service, queueThreadFactory);
        }
       
        if (isBatched) {
            service = new BatchApnsService(conn, feedback, batchWaitTimeInSec, batchMaxWaitTimeInSec, batchThreadFactory);
        }

        service.start();

        return service;
    }

    private void checkInitialization() {
        if (sslContext == null)
            throw new IllegalStateException(
                    "SSL Certificates and attribute are not initialized\n"
                    + "Use .withCert() methods.");
        if (gatewayHost == null || gatewayPort == -1)
            throw new IllegalStateException(
                    "The Destination APNS server is not stated\n"
                    + "Use .withDestination(), withSandboxDestination(), "
                    + "or withProductionDestination().");
    }
}
TOP

Related Classes of com.notnoop.apns.ApnsServiceBuilder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.