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