001package com.pusher.client.util; 002 003import com.pusher.client.ChannelAuthorizer; 004import com.pusher.client.PusherOptions; 005import com.pusher.client.UserAuthenticator; 006import com.pusher.client.channel.PusherEvent; 007import com.pusher.client.channel.impl.ChannelImpl; 008import com.pusher.client.channel.impl.ChannelManager; 009import com.pusher.client.channel.impl.PresenceChannelImpl; 010import com.pusher.client.channel.impl.PrivateChannelImpl; 011import com.pusher.client.channel.impl.PrivateEncryptedChannelImpl; 012import com.pusher.client.connection.impl.InternalConnection; 013import com.pusher.client.connection.websocket.WebSocketClientWrapper; 014import com.pusher.client.connection.websocket.WebSocketConnection; 015import com.pusher.client.connection.websocket.WebSocketListener; 016import com.pusher.client.crypto.nacl.SecretBoxOpenerFactory; 017import com.pusher.client.user.impl.InternalUser; 018 019import java.net.Proxy; 020import java.net.URI; 021import java.net.URISyntaxException; 022import java.util.concurrent.ExecutorService; 023import java.util.concurrent.Executors; 024import java.util.concurrent.ScheduledExecutorService; 025import java.util.concurrent.ThreadFactory; 026import java.util.function.Consumer; 027 028import javax.net.ssl.SSLException; 029 030/** 031 * This is a lightweight way of doing dependency injection and enabling classes 032 * to be unit tested in isolation. No class in this library instantiates another 033 * class directly, otherwise they would be tightly coupled. Instead, they all 034 * call the factory methods in this class when they want to create instances of 035 * another class. 036 * <p> 037 * An instance of Factory is provided on construction to each class which may 038 * require it, the initial factory is instantiated in the Pusher constructor, 039 * the only constructor which a library consumer should need to call directly. 040 * <p> 041 * Conventions: 042 * <p> 043 * - any method that starts with "new", such as 044 * {@link #newPublicChannel(String)} creates a new instance of that class every 045 * time it is called. 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( 056 final String apiKey, 057 final PusherOptions options, 058 final Consumer<PusherEvent> eventHandler 059 ) { 060 if (connection == null) { 061 try { 062 connection = 063 new WebSocketConnection( 064 options.buildUrl(apiKey), 065 options.getActivityTimeout(), 066 options.getPongTimeout(), 067 options.getMaxReconnectionAttempts(), 068 options.getMaxReconnectGapInSeconds(), 069 options.getProxy(), 070 eventHandler, 071 this 072 ); 073 } catch (final URISyntaxException e) { 074 throw new IllegalArgumentException("Failed to initialise connection", e); 075 } 076 } 077 return connection; 078 } 079 080 public WebSocketClientWrapper newWebSocketClientWrapper( 081 final URI uri, 082 final Proxy proxy, 083 final WebSocketListener webSocketListener 084 ) throws SSLException { 085 return new WebSocketClientWrapper(uri, proxy, webSocketListener); 086 } 087 088 public synchronized ScheduledExecutorService getTimers() { 089 if (timers == null) { 090 timers = Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory("timers")); 091 } 092 return timers; 093 } 094 095 public ChannelImpl newPublicChannel(final String channelName) { 096 return new ChannelImpl(channelName, this); 097 } 098 099 public PrivateChannelImpl newPrivateChannel( 100 final InternalConnection connection, 101 final String channelName, 102 final ChannelAuthorizer channelAuthorizer 103 ) { 104 return new PrivateChannelImpl(connection, channelName, channelAuthorizer, this); 105 } 106 107 public PrivateEncryptedChannelImpl newPrivateEncryptedChannel( 108 final InternalConnection connection, 109 final String channelName, 110 final ChannelAuthorizer channelAuthorizer 111 ) { 112 return new PrivateEncryptedChannelImpl(connection, channelName, channelAuthorizer, this, new SecretBoxOpenerFactory()); 113 } 114 115 public PresenceChannelImpl newPresenceChannel( 116 final InternalConnection connection, 117 final String channelName, 118 final ChannelAuthorizer channelAuthorizer 119 ) { 120 return new PresenceChannelImpl(connection, channelName, channelAuthorizer, this); 121 } 122 123 public InternalUser newUser(InternalConnection connection, UserAuthenticator userAuthenticator) { 124 return new InternalUser(connection, userAuthenticator, this); 125 } 126 127 public synchronized ChannelManager getChannelManager() { 128 if (channelManager == null) { 129 channelManager = new ChannelManager(this); 130 } 131 return channelManager; 132 } 133 134 public synchronized void queueOnEventThread(final Runnable r) { 135 if (eventQueue == null) { 136 eventQueue = Executors.newSingleThreadExecutor(new DaemonThreadFactory("eventQueue")); 137 } 138 eventQueue.execute(() -> { 139 synchronized (eventLock) { 140 r.run(); 141 } 142 }); 143 } 144 145 public synchronized void shutdownThreads() { 146 if (eventQueue != null) { 147 eventQueue.shutdown(); 148 eventQueue = null; 149 } 150 if (timers != null) { 151 timers.shutdown(); 152 timers = null; 153 } 154 } 155 156 private static class DaemonThreadFactory implements ThreadFactory { 157 158 private final String name; 159 160 public DaemonThreadFactory(final String name) { 161 this.name = name; 162 } 163 164 @Override 165 public Thread newThread(final Runnable r) { 166 final Thread t = new Thread(r); 167 t.setDaemon(true); 168 t.setName("pusher-java-client " + name); 169 return t; 170 } 171 } 172}