001package com.pusher.client;
002
003import java.io.IOException;
004import java.io.InputStream;
005import java.util.Properties;
006
007/**
008 * Configuration for a {@link com.pusher.client.Pusher} instance.
009 */
010public class PusherOptions {
011
012    private static final String SRC_LIB_DEV_VERSION = "@version@";
013    private static final String LIB_DEV_VERSION = "0.0.0-dev";
014        public static final String LIB_VERSION = readVersionFromProperties();
015
016    private static final String URI_SUFFIX = "?client=java-client&protocol=5&version=" + LIB_VERSION;
017    private static final String WS_SCHEME = "ws";
018    private static final String WSS_SCHEME = "wss";
019
020    private static final int WS_PORT = 80;
021    private static final int WSS_PORT = 443;
022    private static final String PUSHER_DOMAIN = "pusher.com";
023
024    private static final long DEFAULT_ACTIVITY_TIMEOUT = 120000;
025    private static final long DEFAULT_PONG_TIMEOUT = 30000;
026
027    // Note that the primary cluster lives on a different domain
028    // (others are subdomains of pusher.com). This is not an oversight.
029    // Legacy reasons.
030    private String host = "ws.pusherapp.com";
031    private int wsPort = WS_PORT;
032    private int wssPort = WSS_PORT;
033    private boolean encrypted = true;
034    private long activityTimeout = DEFAULT_ACTIVITY_TIMEOUT;
035    private long pongTimeout = DEFAULT_PONG_TIMEOUT;
036    private Authorizer authorizer;
037
038    /**
039     * Gets whether an encrypted (SSL) connection should be used when connecting
040     * to Pusher.
041     *
042     * @return true if an encrypted connection should be used; otherwise false.
043     */
044    public boolean isEncrypted() {
045        return encrypted;
046    }
047
048    /**
049     * Sets whether an encrypted (SSL) connection should be used when connecting to
050     * Pusher.
051     *
052     * @param encrypted Whether to use an SSL connection
053     * @return this, for chaining
054     */
055    public PusherOptions setEncrypted(final boolean encrypted) {
056        this.encrypted = encrypted;
057        return this;
058    }
059
060    /**
061     * Gets the authorizer to be used when authenticating private and presence
062     * channels.
063     *
064     * @return the authorizer
065     */
066    public Authorizer getAuthorizer() {
067        return authorizer;
068    }
069
070    /**
071     * Sets the authorizer to be used when authenticating private and presence
072     * channels.
073     *
074     * @param authorizer
075     *            The authorizer to be used.
076     * @return this, for chaining
077     */
078    public PusherOptions setAuthorizer(final Authorizer authorizer) {
079        this.authorizer = authorizer;
080        return this;
081    }
082
083    /**
084     * The host to which connections will be made.
085     *
086     * Note that if you wish to connect to a standard Pusher cluster, the
087     * convenience method setCluster will set the host and ports correctly from
088     * a single argument.
089     *
090     * @param host The host
091     * @return this, for chaining
092     */
093    public PusherOptions setHost(final String host) {
094        this.host = host;
095        return this;
096    }
097
098    /**
099     * The port to which unencrypted connections will be made.
100     *
101     * Note that if you wish to connect to a standard Pusher cluster, the
102     * convenience method setCluster will set the host and ports correctly from
103     * a single argument.
104     *
105     * @param wsPort port number
106     * @return this, for chaining
107     */
108    public PusherOptions setWsPort(final int wsPort) {
109        this.wsPort = wsPort;
110        return this;
111    }
112
113    /**
114     * The port to which encrypted connections will be made.
115     *
116     * Note that if you wish to connect to a standard Pusher cluster, the
117     * convenience method setCluster will set the host and ports correctly from
118     * a single argument.
119     *
120     * @param wssPort port number
121     * @return this, for chaining
122     */
123    public PusherOptions setWssPort(final int wssPort) {
124        this.wssPort = wssPort;
125        return this;
126    }
127
128    public PusherOptions setCluster(final String cluster) {
129        host = "ws-" + cluster + "." + PUSHER_DOMAIN;
130        wsPort = WS_PORT;
131        wssPort = WSS_PORT;
132        return this;
133    }
134
135    /**
136     * The number of milliseconds of inactivity at which a "ping" will be
137     * triggered to check the connection.
138     *
139     * The default value is 120,000 (2 minutes). On some connections, where
140     * intermediate hops between the application and Pusher are aggressively
141     * culling connections they consider to be idle, a lower value may help
142     * preserve the connection.
143     *
144     * @param activityTimeout
145     *            time to consider connection idle, in milliseconds
146     * @return this, for chaining
147     */
148    public PusherOptions setActivityTimeout(final long activityTimeout) {
149        if (activityTimeout < 1000) {
150            throw new IllegalArgumentException(
151                    "Activity timeout must be at least 1,000ms (and is recommended to be much higher)");
152        }
153
154        this.activityTimeout = activityTimeout;
155        return this;
156    }
157
158    public long getActivityTimeout() {
159        return activityTimeout;
160    }
161
162    /**
163     * The number of milliseconds after a "ping" is sent that the client will
164     * wait to receive a "pong" response from the server before considering the
165     * connection broken and triggering a transition to the disconnected state.
166     *
167     * The default value is 30,000.
168     *
169     * @param pongTimeout
170     *            time to wait for pong response, in milliseconds
171     * @return this, for chaining
172     */
173    public PusherOptions setPongTimeout(final long pongTimeout) {
174        if (pongTimeout < 1000) {
175            throw new IllegalArgumentException(
176                    "Pong timeout must be at least 1,000ms (and is recommended to be much higher)");
177        }
178
179        this.pongTimeout = pongTimeout;
180        return this;
181    }
182
183    public long getPongTimeout() {
184        return pongTimeout;
185    }
186
187    /**
188     * Construct the URL for the WebSocket connection based on the options
189     * previous set on this object and the provided API key
190     *
191     * @param apiKey The API key
192     * @return the WebSocket URL
193     */
194    public String buildUrl(final String apiKey) {
195        return String.format("%s://%s:%s/app/%s%s", encrypted ? WSS_SCHEME : WS_SCHEME, host, encrypted ? wssPort
196                : wsPort, apiKey, URI_SUFFIX);
197    }
198
199    private static String readVersionFromProperties() {
200        InputStream inStream = null;
201        try {
202            final Properties p = new Properties();
203            inStream = PusherOptions.class.getResourceAsStream("/pusher.properties");
204            p.load(inStream);
205            String version = (String)p.get("version");
206            
207            // If the properties file contents indicates the version is being run
208            // from source then replace with a dev indicator. Otherwise the Pusher
209            // Socket API will reject the connection.
210            if(version.equals(SRC_LIB_DEV_VERSION)) {
211                version = LIB_DEV_VERSION;
212            }
213            
214            if (version != null && version.length() > 0) {
215                return version;
216            }
217        }
218        catch (final Exception e) {
219            // Fall back to fixed value
220        }
221        finally {
222            try {
223                if (inStream != null) {
224                    inStream.close();
225                }
226            }
227            catch (final IOException e) {
228                // Ignore problem closing stream
229            }
230        }
231        return "0.0.0";
232    }
233}