001package com.pusher.client; 002 003import com.pusher.client.channel.Channel; 004import com.pusher.client.channel.ChannelEventListener; 005import com.pusher.client.channel.PresenceChannel; 006import com.pusher.client.channel.PresenceChannelEventListener; 007import com.pusher.client.channel.PrivateChannel; 008import com.pusher.client.channel.PrivateChannelEventListener; 009import com.pusher.client.channel.SubscriptionEventListener; 010import com.pusher.client.channel.impl.ChannelManager; 011import com.pusher.client.channel.impl.InternalChannel; 012import com.pusher.client.channel.impl.PresenceChannelImpl; 013import com.pusher.client.channel.impl.PrivateChannelImpl; 014import com.pusher.client.connection.Connection; 015import com.pusher.client.connection.ConnectionEventListener; 016import com.pusher.client.connection.ConnectionState; 017import com.pusher.client.connection.impl.InternalConnection; 018import com.pusher.client.util.Factory; 019 020/** 021 * This class is the main entry point for accessing Pusher. 022 * 023 * <p> 024 * By creating a new {@link Pusher} instance and calling {@link 025 * Pusher#connect()} a connection to Pusher is established. 026 * </p> 027 * 028 * <p> 029 * Subscriptions for data are represented by 030 * {@link com.pusher.client.channel.Channel} objects, or subclasses thereof. 031 * Subscriptions are created by calling {@link Pusher#subscribe(String)}, 032 * {@link Pusher#subscribePrivate(String)}, 033 * {@link Pusher#subscribePresence(String)} or one of the overloads. 034 * </p> 035 */ 036public class Pusher { 037 038 private final PusherOptions pusherOptions; 039 private final InternalConnection connection; 040 private final ChannelManager channelManager; 041 private final Factory factory; 042 043 /** 044 * Creates a new instance of Pusher. 045 * 046 * <p> 047 * Note that if you use this constructor you will not be able to subscribe 048 * to private or presence channels because no {@link Authorizer} has been 049 * set. If you want to use private or presence channels: 050 * <ul> 051 * <li>Create an implementation of the {@link Authorizer} interface, or use 052 * the {@link com.pusher.client.util.HttpAuthorizer} provided.</li> 053 * <li>Create an instance of {@link PusherOptions} and set the authorizer on 054 * it by calling {@link PusherOptions#setAuthorizer(Authorizer)}.</li> 055 * <li>Use the {@link #Pusher(String, PusherOptions)} constructor to create 056 * an instance of Pusher.</li> 057 * </ul> 058 * 059 * <p> 060 * The {@link com.pusher.client.example.PrivateChannelExampleApp} and 061 * {@link com.pusher.client.example.PresenceChannelExampleApp} example 062 * applications show how to do this. 063 * </p> 064 * 065 * @param apiKey 066 * Your Pusher API key. 067 */ 068 public Pusher(final String apiKey) { 069 070 this(apiKey, new PusherOptions()); 071 } 072 073 /** 074 * Creates a new instance of Pusher. 075 * 076 * @param apiKey 077 * Your Pusher API key. 078 * @param pusherOptions 079 * Options for the Pusher client library to use. 080 */ 081 public Pusher(final String apiKey, final PusherOptions pusherOptions) { 082 083 this(apiKey, pusherOptions, new Factory()); 084 } 085 086 /** 087 * Creates a new Pusher instance using the provided Factory, package level 088 * access for unit tests only. 089 */ 090 Pusher(final String apiKey, final PusherOptions pusherOptions, final Factory factory) { 091 092 if (apiKey == null || apiKey.length() == 0) { 093 throw new IllegalArgumentException("API Key cannot be null or empty"); 094 } 095 096 if (pusherOptions == null) { 097 throw new IllegalArgumentException("PusherOptions cannot be null"); 098 } 099 100 this.pusherOptions = pusherOptions; 101 this.factory = factory; 102 connection = factory.getConnection(apiKey, this.pusherOptions); 103 channelManager = factory.getChannelManager(); 104 channelManager.setConnection(connection); 105 } 106 107 /* Connection methods */ 108 109 /** 110 * Gets the underlying {@link Connection} object that is being used by this 111 * instance of {@linkplain Pusher}. 112 * 113 * @return The {@link Connection} object. 114 */ 115 public Connection getConnection() { 116 return connection; 117 } 118 119 /** 120 * Connects to Pusher. Any {@link ConnectionEventListener}s that have 121 * already been registered using the 122 * {@link Connection#bind(ConnectionState, ConnectionEventListener)} method 123 * will receive connection events. 124 * 125 * <p>Calls are ignored (a connection is not attempted) if the {@link Connection#getState()} is not {@link com.pusher.client.connection.ConnectionState#DISCONNECTED}.</p> 126 */ 127 public void connect() { 128 connect(null); 129 } 130 131 /** 132 * Binds a {@link ConnectionEventListener} to the specified events and then 133 * connects to Pusher. This is equivalent to binding a 134 * {@link ConnectionEventListener} using the 135 * {@link Connection#bind(ConnectionState, ConnectionEventListener)} method 136 * before connecting. 137 * 138 <p>Calls are ignored (a connection is not attempted) if the {@link Connection#getState()} is not {@link com.pusher.client.connection.ConnectionState#DISCONNECTED}.</p> 139 * 140 * @param eventListener 141 * A {@link ConnectionEventListener} that will receive connection 142 * events. This can be null if you are not interested in 143 * receiving connection events, in which case you should call 144 * {@link #connect()} instead of this method. 145 * @param connectionStates 146 * An optional list of {@link ConnectionState}s to bind your 147 * {@link ConnectionEventListener} to before connecting to 148 * Pusher. If you do not specify any {@link ConnectionState}s 149 * then your {@link ConnectionEventListener} will be bound to all 150 * connection events. This is equivalent to calling 151 * {@link #connect(ConnectionEventListener, ConnectionState...)} 152 * with {@link ConnectionState#ALL}. 153 * @throws IllegalArgumentException 154 * If the {@link ConnectionEventListener} is null and at least 155 * one connection state has been specified. 156 */ 157 public void connect(final ConnectionEventListener eventListener, ConnectionState... connectionStates) { 158 159 if (eventListener != null) { 160 if (connectionStates.length == 0) { 161 connectionStates = new ConnectionState[] { ConnectionState.ALL }; 162 } 163 164 for (final ConnectionState state : connectionStates) { 165 connection.bind(state, eventListener); 166 } 167 } 168 else { 169 if (connectionStates.length > 0) { 170 throw new IllegalArgumentException( 171 "Cannot bind to connection states with a null connection event listener"); 172 } 173 } 174 175 connection.connect(); 176 } 177 178 /** 179 * Disconnect from Pusher. 180 * 181 * <p> 182 * Calls are ignored if the {@link Connection#getState()}, retrieved from {@link Pusher#getConnection}, is not 183 * {@link com.pusher.client.connection.ConnectionState#CONNECTED}. 184 * </p> 185 */ 186 public void disconnect() { 187 if (connection.getState() == ConnectionState.CONNECTED) { 188 connection.disconnect(); 189 } 190 } 191 192 /* Subscription methods */ 193 194 /** 195 * Subscribes to a public {@link Channel}. 196 * 197 * Note that subscriptions should be registered only once with a Pusher 198 * instance. Subscriptions are persisted over disconnection and 199 * re-registered with the server automatically on reconnection. This means 200 * that subscriptions may also be registered before connect() is called, 201 * they will be initiated on connection. 202 * 203 * @param channelName 204 * The name of the {@link Channel} to subscribe to. 205 * @return The {@link Channel} object representing your subscription. 206 */ 207 public Channel subscribe(final String channelName) { 208 return subscribe(channelName, null); 209 } 210 211 /** 212 * Binds a {@link ChannelEventListener} to the specified events and then 213 * subscribes to a public {@link Channel}. 214 * 215 * @param channelName 216 * The name of the {@link Channel} to subscribe to. 217 * @param listener 218 * A {@link ChannelEventListener} to receive events. This can be 219 * null if you don't want to bind a listener at subscription 220 * time, in which case you should call {@link #subscribe(String)} 221 * instead of this method. 222 * @param eventNames 223 * An optional list of event names to bind your 224 * {@link ChannelEventListener} to before subscribing. 225 * @return The {@link Channel} object representing your subscription. 226 * @throws IllegalArgumentException 227 * If any of the following are true: 228 * <ul> 229 * <li>The channel name is null.</li> 230 * <li>You are already subscribed to this channel.</li> 231 * <li>The channel name starts with "private-". If you want to 232 * subscribe to a private channel, call 233 * {@link #subscribePrivate(String, PrivateChannelEventListener, String...)} 234 * instead of this method.</li> 235 * <li>At least one of the specified event names is null.</li> 236 * <li>You have specified at least one event name and your 237 * {@link ChannelEventListener} is null.</li> 238 * </ul> 239 */ 240 public Channel subscribe(final String channelName, final ChannelEventListener listener, final String... eventNames) { 241 242 final InternalChannel channel = factory.newPublicChannel(channelName); 243 channelManager.subscribeTo(channel, listener, eventNames); 244 245 return channel; 246 } 247 248 /** 249 * Subscribes to a {@link com.pusher.client.channel.PrivateChannel} which 250 * requires authentication. 251 * 252 * @param channelName 253 * The name of the channel to subscribe to. 254 * @return A new {@link com.pusher.client.channel.PrivateChannel} 255 * representing the subscription. 256 * @throws IllegalStateException 257 * if a {@link com.pusher.client.Authorizer} has not been set 258 * for the {@link Pusher} instance via 259 * {@link #Pusher(String, PusherOptions)}. 260 */ 261 public PrivateChannel subscribePrivate(final String channelName) { 262 return subscribePrivate(channelName, null); 263 } 264 265 /** 266 * Subscribes to a {@link com.pusher.client.channel.PrivateChannel} which 267 * requires authentication. 268 * 269 * @param channelName The name of the channel to subscribe to. 270 * @param listener A listener to be informed of both Pusher channel protocol events and subscription data events. 271 * @param eventNames An optional list of names of events to be bound to on the channel. The equivalent of calling {@link com.pusher.client.channel.Channel#bind(String, SubscriptionEventListener)} on or more times. 272 * @return A new {@link com.pusher.client.channel.PrivateChannel} representing the subscription. 273 * @throws IllegalStateException if a {@link com.pusher.client.Authorizer} has not been set for the {@link Pusher} instance via {@link #Pusher(String, PusherOptions)}. 274 */ 275 public PrivateChannel subscribePrivate(final String channelName, final PrivateChannelEventListener listener, 276 final String... eventNames) { 277 278 throwExceptionIfNoAuthorizerHasBeenSet(); 279 280 final PrivateChannelImpl channel = factory.newPrivateChannel(connection, channelName, 281 pusherOptions.getAuthorizer()); 282 channelManager.subscribeTo(channel, listener, eventNames); 283 284 return channel; 285 } 286 287 /** 288 * Subscribes to a {@link com.pusher.client.channel.PresenceChannel} which 289 * requires authentication. 290 * 291 * @param channelName 292 * The name of the channel to subscribe to. 293 * @return A new {@link com.pusher.client.channel.PresenceChannel} 294 * representing the subscription. 295 * @throws IllegalStateException 296 * if a {@link com.pusher.client.Authorizer} has not been set 297 * for the {@link Pusher} instance via 298 * {@link #Pusher(String, PusherOptions)}. 299 */ 300 public PresenceChannel subscribePresence(final String channelName) { 301 return subscribePresence(channelName, null); 302 } 303 304 /** 305 * Subscribes to a {@link com.pusher.client.channel.PresenceChannel} which 306 * requires authentication. 307 * 308 * @param channelName The name of the channel to subscribe to. 309 * @param listener A listener to be informed of Pusher channel protocol, including presence-specific events, and subscription data events. 310 * @param eventNames An optional list of names of events to be bound to on the channel. The equivalent of calling {@link com.pusher.client.channel.Channel#bind(String, SubscriptionEventListener)} on or more times. 311 * @return A new {@link com.pusher.client.channel.PresenceChannel} representing the subscription. 312 * @throws IllegalStateException if a {@link com.pusher.client.Authorizer} has not been set for the {@link Pusher} instance via {@link #Pusher(String, PusherOptions)}. 313 */ 314 public PresenceChannel subscribePresence(final String channelName, final PresenceChannelEventListener listener, 315 final String... eventNames) { 316 317 throwExceptionIfNoAuthorizerHasBeenSet(); 318 319 final PresenceChannelImpl channel = factory.newPresenceChannel(connection, channelName, 320 pusherOptions.getAuthorizer()); 321 channelManager.subscribeTo(channel, listener, eventNames); 322 323 return channel; 324 } 325 326 /** 327 * Unsubscribes from a channel using via the name of the channel. 328 * 329 * @param channelName 330 * the name of the channel to be unsubscribed from. 331 */ 332 public void unsubscribe(final String channelName) { 333 334 channelManager.unsubscribeFrom(channelName); 335 } 336 337 /* implementation detail */ 338 339 private void throwExceptionIfNoAuthorizerHasBeenSet() { 340 if (pusherOptions.getAuthorizer() == null) { 341 throw new IllegalStateException( 342 "Cannot subscribe to a private or presence channel because no Authorizer has been set. Call PusherOptions.setAuthorizer() before connecting to Pusher"); 343 } 344 } 345 346 /** 347 * 348 * @param channelName The name of the public channel to be retrieved 349 * @return A public channel, or null if it could not be found 350 * @throws IllegalArgumentException if you try to retrieve a private or presence channel. 351 */ 352 public Channel getChannel(String channelName){ 353 return channelManager.getChannel(channelName); 354 } 355 356 /** 357 * 358 * @param channelName The name of the private channel to be retrieved 359 * @return A private channel, or null if it could not be found 360 * @throws IllegalArgumentException if you try to retrieve a public or presence channel. 361 */ 362 public PrivateChannel getPrivateChannel(String channelName){ 363 return channelManager.getPrivateChannel(channelName); 364 } 365 366 /** 367 * 368 * @param channelName The name of the presence channel to be retrieved 369 * @return A presence channel, or null if it could not be found 370 * @throws IllegalArgumentException if you try to retrieve a public or private channel. 371 */ 372 public PresenceChannel getPresenceChannel(String channelName){ 373 return channelManager.getPresenceChannel(channelName); 374 } 375 376}