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}