001package com.pusher.client.util; 002 003import java.net.Proxy; 004import java.net.URI; 005import java.net.URISyntaxException; 006import java.util.concurrent.ExecutorService; 007import java.util.concurrent.Executors; 008import java.util.concurrent.ScheduledExecutorService; 009import java.util.concurrent.ThreadFactory; 010 011import javax.net.ssl.SSLException; 012 013import org.java_websocket.client.WebSocketClient; 014 015import com.pusher.client.Authorizer; 016import com.pusher.client.PusherOptions; 017import com.pusher.client.channel.impl.ChannelImpl; 018import com.pusher.client.channel.impl.ChannelManager; 019import com.pusher.client.channel.impl.PresenceChannelImpl; 020import com.pusher.client.channel.impl.PrivateChannelImpl; 021import com.pusher.client.connection.impl.InternalConnection; 022import com.pusher.client.connection.websocket.WebSocketClientWrapper; 023import com.pusher.client.connection.websocket.WebSocketConnection; 024import com.pusher.client.connection.websocket.WebSocketListener; 025 026/** 027 * This is a lightweight way of doing dependency injection and enabling classes 028 * to be unit tested in isolation. No class in this library instantiates another 029 * class directly, otherwise they would be tightly coupled. Instead, they all 030 * call the factory methods in this class when they want to create instances of 031 * another class. 032 * 033 * An instance of Factory is provided on construction to each class which may 034 * require it, the initial factory is instantiated in the Pusher constructor, 035 * the only constructor which a library consumer should need to call directly. 036 * 037 * Conventions: 038 * 039 * - any method that starts with "new", such as 040 * {@link #newPublicChannel(String)} creates a new instance of that class every 041 * time it is called. 042 * 043 * - any method that starts with "get", such as {@link #getEventQueue()} returns 044 * a singleton. These are lazily constructed and their access methods should be 045 * synchronized for this reason. 046 */ 047public class Factory { 048 049 private InternalConnection connection; 050 private ChannelManager channelManager; 051 private ExecutorService eventQueue; 052 private ScheduledExecutorService timers; 053 private static final Object eventLock = new Object(); 054 055 public synchronized InternalConnection getConnection(final String apiKey, final PusherOptions options) { 056 if (connection == null) { 057 try { 058 connection = new WebSocketConnection(options.buildUrl(apiKey), options.getActivityTimeout(), 059 options.getPongTimeout(), options.getProxy(), this); 060 } 061 catch (final URISyntaxException e) { 062 throw new IllegalArgumentException("Failed to initialise connection", e); 063 } 064 } 065 return connection; 066 } 067 068 public WebSocketClient newWebSocketClientWrapper(final URI uri, final Proxy proxy, final WebSocketListener webSocketListener) throws SSLException { 069 return new WebSocketClientWrapper(uri, proxy, webSocketListener); 070 } 071 072 public synchronized ScheduledExecutorService getTimers() { 073 if (timers == null) { 074 timers = Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory("timers")); 075 } 076 return timers; 077 } 078 079 public ChannelImpl newPublicChannel(final String channelName) { 080 return new ChannelImpl(channelName, this); 081 } 082 083 public PrivateChannelImpl newPrivateChannel(final InternalConnection connection, final String channelName, 084 final Authorizer authorizer) { 085 return new PrivateChannelImpl(connection, channelName, authorizer, this); 086 } 087 088 public PresenceChannelImpl newPresenceChannel(final InternalConnection connection, final String channelName, 089 final Authorizer authorizer) { 090 return new PresenceChannelImpl(connection, channelName, authorizer, this); 091 } 092 093 public synchronized ChannelManager getChannelManager() { 094 if (channelManager == null) { 095 channelManager = new ChannelManager(this); 096 } 097 return channelManager; 098 } 099 100 public void queueOnEventThread(final Runnable r) { 101 getEventQueue().execute(new Runnable() { 102 @Override 103 public void run() { 104 synchronized (eventLock) { 105 r.run(); 106 } 107 } 108 }); 109 } 110 111 public synchronized void shutdownThreads() { 112 if (eventQueue != null) { 113 eventQueue.shutdown(); 114 eventQueue = null; 115 } 116 if (timers != null) { 117 timers.shutdown(); 118 timers = null; 119 } 120 } 121 122 private synchronized ExecutorService getEventQueue() { 123 if (eventQueue == null) { 124 eventQueue = Executors.newSingleThreadExecutor(new DaemonThreadFactory("eventQueue")); 125 } 126 return eventQueue; 127 } 128 129 private static class DaemonThreadFactory implements ThreadFactory { 130 private final String name; 131 132 public DaemonThreadFactory(final String name) { 133 this.name = name; 134 } 135 136 @Override 137 public Thread newThread(final Runnable r) { 138 final Thread t = new Thread(r); 139 t.setDaemon(true); 140 t.setName("pusher-java-client " + name); 141 return t; 142 } 143 } 144}