Java tutorial
/* * Copyright 2013 John Ahlroos * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package fi.jasoft.remoteconnection.client; import java.util.LinkedList; import java.util.List; import java.util.logging.Logger; import com.google.gwt.core.client.Callback; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.ScriptInjector; import com.google.gwt.dev.util.Util; import com.google.gwt.json.client.JSONObject; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import elemental.js.util.Json; import fi.jasoft.remoteconnection.client.peer.DataConnection; import fi.jasoft.remoteconnection.client.peer.ObjectPeerListener; import fi.jasoft.remoteconnection.client.peer.Peer; import fi.jasoft.remoteconnection.client.peer.PeerError; import fi.jasoft.remoteconnection.client.peer.PeerListener; import fi.jasoft.remoteconnection.client.peer.PeerOptions; import fi.jasoft.remoteconnection.client.peer.StringPeerListener; import fi.jasoft.remoteconnection.shared.ConnectedListener; import fi.jasoft.remoteconnection.shared.ConnectionError; import fi.jasoft.remoteconnection.shared.IncomingChannelConnectionListener; import fi.jasoft.remoteconnection.shared.RemoteChannel; import fi.jasoft.remoteconnection.shared.RemoteConnection; import fi.jasoft.remoteconnection.shared.RemoteConnectionDataListener; import fi.jasoft.remoteconnection.shared.RemoteConnectionErrorHandler; import fi.jasoft.remoteconnection.shared.RemoteConnectionConfiguration; /** * Client side implementation of {@link RemoteConnection}. * Use {@link ClientRemoteConnection#register()} to get a instance. * * @author John Ahlroos */ public class ClientRemoteConnection implements RemoteConnection { /** * Client side implementation of RemoteConnection. Use {@link ClientRemoteConnection#register()} to get an instance. * * @author John Ahlroos */ public class ClientRemoteChannel implements RemoteChannel { private final String id; private DataConnection connection; private List<String> messageQueue = new LinkedList<String>(); private final List<RemoteConnectionDataListener> listeners = new LinkedList<RemoteConnectionDataListener>(); private final List<ConnectedListener> connectedListeners = new LinkedList<ConnectedListener>(); @Override public void send(String message) { if (isConnected()) { ClientRemoteConnection.getLogger().info("Sending message to " + id); connection.send(message); } else { ClientRemoteConnection.getLogger() .warning("No connection to channel endpoint. Queueing message for later."); messageQueue.add(message); } } @Override public String getId() { return id; } @Override public boolean isConnected() { return connection != null && connection.isOpen(); } @Override public void addConnectedListener(ConnectedListener listener) { connectedListeners.add(listener); } /** * Default constructor * @param id */ private ClientRemoteChannel(String id) { this.id = id; } /** * Adds a data listener to the channel * * @param listener * The listener to add */ private void addDataListener(RemoteConnectionDataListener listener) { listeners.add(listener); } private void setConnection(final DataConnection con) { if (con == null) { throw new IllegalArgumentException("Connection cannot be null"); } this.connection = con; ClientRemoteConnection.getLogger().info("Opening channel connection to " + connection.getPeerId()); connection.addListener("open", new PeerListener() { @Override public void execute() { ClientRemoteConnection.getLogger().info("Connected to channel " + getId()); ; flushMessageQueue(); for (ConnectedListener listener : connectedListeners) { listener.connected(getId()); } } }); connection.addDataListener(new StringPeerListener() { @Override public void execute(String str) { messageRecieved(str); } }); } private void flushMessageQueue() { while (!messageQueue.isEmpty()) { this.send(messageQueue.remove(0)); } } private void messageRecieved(String message) { for (RemoteConnectionDataListener listener : listeners) { listener.dataRecieved(this, message); } } } // Currently open connectedChannels private final List<ClientRemoteChannel> connectedChannels = new LinkedList<ClientRemoteChannel>(); private final List<ClientRemoteChannel> pendingConnectionChannels = new LinkedList<ClientRemoteChannel>(); private final List<RemoteConnectionDataListener> listeners = new LinkedList<RemoteConnectionDataListener>(); private final List<IncomingChannelConnectionListener> incomingListeners = new LinkedList<IncomingChannelConnectionListener>(); private final List<ConnectedListener> connectedListeners = new LinkedList<ConnectedListener>(); private Peer peer; private boolean connectedToSignallingServer = false; private RemoteConnectionErrorHandler errorHandler; private static String PEER_JS_URL = "http://cdn.peerjs.com/0.3.4/peer.min.js"; private boolean scriptLoaded = false; private boolean scriptFailedToLoad = false; private final RemoteConnectionConfiguration configuration; static Logger getLogger() { return Logger.getLogger(ClientRemoteConnection.class.getName()); } private Timer connectionTimeoutTimer = new Timer() { @Override public void run() { getLogger().severe("Remote connection timed out"); } }; /** * Registers a new remote connection. Id is autogenerated by the signalling server. */ public static RemoteConnection register() { return register(new RemoteConnectionConfiguration()); } /** * Register a new Remote connection with a specific id * * @param id * The unique id of the connection * @return */ public static RemoteConnection register(RemoteConnectionConfiguration configuration) { return new ClientRemoteConnection(configuration); } private ClientRemoteConnection(RemoteConnectionConfiguration configuration) { this.configuration = configuration; this.scriptLoaded = !configuration.isScriptInjected(); } private final native boolean isPeerAvailable() /*-{ try{ return typeof($wnd.Peer) === 'function'; } catch(e){ return false; } }-*/; @Override public void connect() { if (peer != null) { throw new IllegalStateException("Already connected, call terminate() before connecting again"); } // Inject the script if needed if (!scriptLoaded && !scriptFailedToLoad && !isPeerAvailable()) { ScriptInjector.fromUrl(PEER_JS_URL).setWindow(ScriptInjector.TOP_WINDOW) .setCallback(new Callback<Void, Exception>() { @Override public void onSuccess(Void result) { getLogger().info("Loaded peer.js successfully"); scriptLoaded = true; if (isPeerAvailable()) { connect(); } else { getLogger().severe("Peer is not available in DOM after loading script. Aborting."); scriptFailedToLoad = true; } } @Override public void onFailure(Exception reason) { scriptFailedToLoad = true; getLogger().severe("Failed to load Peer.js from " + PEER_JS_URL); } }).inject(); return; } if (configuration.getKey().equals(RemoteConnectionConfiguration.DEVELOPMENT_PEER_JS_KEY)) { getLogger().warning("You are using the development key of RemoteConnection " + "with a very limited amount of connections shared among all " + "RemoteConnection users. You are strongly encoraged to apply " + "for your own developer key at http://peerjs.com/peerserver or " + "run your own server which can be downloaded from https://github.com/peers/peerjs-server. " + "You can supply your own peer server details through the RemoteConnection.getConfiguration() " + "option. Thank you."); } if (!isPeerAvailable()) { throw new IllegalStateException("Peer library is missing from DOM. Cannot connect."); } if (!scriptLoaded) { throw new IllegalStateException("Peer script is not loaded. Cannot connect."); } // Create peer try { peer = Peer.create(configuration.getId(), getOptionsFromConfiguration(configuration)); } catch (Exception e) { throw new RuntimeException("Could not create peer connection.", e); } // Register with signaling server peer.addListener("open", new StringPeerListener() { @Override public void execute(String peerId) { connectionTimeoutTimer.cancel(); onOpen(peerId); } }); // Triggered when another remote connection is established peer.addListener("connection", new ObjectPeerListener() { @Override public void execute(JavaScriptObject obj) { onConnection((DataConnection) obj); } }); // Triggered when an error occurs peer.addListener("error", new ObjectPeerListener() { @Override public void execute(JavaScriptObject obj) { connectionTimeoutTimer.cancel(); onError((PeerError) obj); } }); // Triggered when the connection is closed peer.addListener("close", new PeerListener() { @Override public void execute() { onClose(); } }); // Listen for timeout connectionTimeoutTimer.schedule(10000); } private static PeerOptions getOptionsFromConfiguration(RemoteConnectionConfiguration configuration) { PeerOptions options = new PeerOptions(); options.key = configuration.getKey(); options.port = configuration.getPort(); options.host = configuration.getHost(); options.secure = configuration.isSecure(); options.config = configuration.getConfig(); options.debug = configuration.getDebug(); return options; } /** * Triggered when a connection to the signalling server has been made * * @param peerId * The peer id recieved from the signalling server */ protected void onOpen(String peerId) { configuration.setId(peerId); connectedToSignallingServer = true; getLogger().info("Connected to signalling server. Listening on id " + configuration.getId()); flushChannelQueue(); for (ConnectedListener listener : connectedListeners) { listener.connected(peerId); } } /** * Trigged when an external connection is being requested * * @param connection * The incoming data connection */ protected void onConnection(DataConnection connection) { ClientRemoteChannel channel = getChannelById(connection.getPeerId()); getLogger().info("Recieved incoming connection from " + connection.getPeerId()); if (channel == null) { channel = new ClientRemoteChannel(connection.getPeerId()); connectToChannel(channel, connection); for (IncomingChannelConnectionListener listener : incomingListeners) { listener.connected(channel); } } } /** * Triggered when the peer is closed. */ protected void onClose() { getLogger().info("Connection closed"); } /** * Triggered when an error occurs with the peer * * @param error * The error that was triggered */ protected void onError(PeerError error) { ConnectionError ce = error.getType(); String msg = (ce == null ? new JSONObject(error).toString() : ce.toString()); if (ce == null) { ce = ConnectionError.CHANNEL_ERROR; } if (errorHandler != null) { getLogger().severe("Remote connection got error: " + msg); if (errorHandler.onConnectionError(ce, msg)) { terminate(); } ; } else { terminate(); getLogger().severe("Remote connection terminated with the error: " + msg); } } /** * Disconnects from the signalling server but leaves all open channels open. * Call {@link #terminate()} to close all open channels as well. */ public void disconnect() { if (isPeerAvailable()) { peer.disconnect(); connectedToSignallingServer = false; } } @Override public void terminate() { if (isPeerAvailable()) { disconnect(); peer.destroy(); peer = null; pendingConnectionChannels.clear(); connectedChannels.clear(); } } private void flushChannelQueue() { while (!pendingConnectionChannels.isEmpty()) { connectToChannel(pendingConnectionChannels.remove(0)); } } private ClientRemoteChannel connectToChannel(ClientRemoteChannel channel) { getLogger().info("Connecting to peer " + channel.getId()); DataConnection connection = peer.connect(channel.getId()); assert connection != null; return connectToChannel(channel, connection); } private ClientRemoteChannel connectToChannel(ClientRemoteChannel channel, DataConnection connection) { channel.setConnection(connection); for (RemoteConnectionDataListener listener : listeners) { channel.addDataListener(listener); } connectedChannels.add(channel); return channel; } @Override public RemoteChannel openChannel(String endpointPeerId) { if (endpointPeerId == null) { throw new IllegalArgumentException("Cannot connect to null channel"); } ClientRemoteChannel channel = getChannelById(endpointPeerId); if (channel != null) { return channel; } channel = new ClientRemoteChannel(endpointPeerId); getLogger().info("Created channel to " + endpointPeerId); if (connectedToSignallingServer) { channel = connectToChannel(channel); } else { getLogger().warning( "Not connected to signalling server, delaying channel connection to " + endpointPeerId); pendingConnectionChannels.add(channel); } return channel; } @Override public void addDataListener(RemoteConnectionDataListener listener) { listeners.add(listener); for (ClientRemoteChannel channel : connectedChannels) { channel.addDataListener(listener); } } @Override public RemoteChannel getChannel(String channelEndpointId) { RemoteChannel channel = getChannelById(channelEndpointId); if (channel == null) { for (ClientRemoteChannel c : pendingConnectionChannels) { if (c.getId().equals(channelEndpointId)) { channel = c; break; } } } return channel; } @Override public void broadcast(String message) { for (RemoteChannel channel : connectedChannels) { channel.send(message); } } @Override public void setErrorHandler(RemoteConnectionErrorHandler handler) { this.errorHandler = handler; } /** * Returns, or creates a new, channel by using its remote peer id * * @param id * The peer id of the channel endpoint * @return */ private ClientRemoteChannel getChannelById(String id) { for (ClientRemoteChannel channel : connectedChannels) { if (channel.getId().equals(id)) { return channel; } } return null; } @Override public boolean isConnected() { return peer != null && isPeerAvailable(); } @Override public void addIncomingConnectionListener(IncomingChannelConnectionListener listener) { incomingListeners.add(listener); } @Override public void addConnectedListener(ConnectedListener listener) { connectedListeners.add(listener); } @Override public RemoteConnectionConfiguration getConfiguration() { return configuration; } }