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}