/*
* Copyright (c) 2014, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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 com.google.dart.tools.debug.core.pubserve;
import com.google.dart.tools.debug.core.DartDebugCorePlugin;
import de.roderick.weberknecht.WebSocket;
import de.roderick.weberknecht.WebSocketEventHandler;
import de.roderick.weberknecht.WebSocketException;
import de.roderick.weberknecht.WebSocketMessage;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A pub protocol websocket connection.
*/
public class PubConnection {
public static interface PubConnectionListener {
public void connectionClosed(PubConnection connection);
}
static interface Callback {
public void handleResult(JSONObject result) throws JSONException;
}
private String host;
private int port;
private String webSocketFile;
private URI webSocketUri;
private WebSocket websocket;
private boolean connected;
private PubCommands commands;
private List<PubConnectionListener> connectionListeners = new ArrayList<PubConnectionListener>();
private Map<Integer, Callback> callbackMap = new HashMap<Integer, Callback>();
private int requestId = 0;
public PubConnection(String host, int port, String webSocketFile) {
this.host = host;
this.port = port;
this.webSocketFile = webSocketFile;
}
public PubConnection(URI webSocketUri) {
this.webSocketUri = webSocketUri;
}
public void addConnectionListener(PubConnectionListener listener) {
connectionListeners.add(listener);
}
public void close() throws IOException {
if (websocket != null) {
try {
websocket.close();
} catch (WebSocketException exception) {
throw new IOException(exception);
} finally {
websocket = null;
}
}
}
public void connect() throws IOException {
try {
if (webSocketUri != null) {
websocket = new WebSocket(webSocketUri);
} else {
websocket = new WebSocket(host, port, webSocketFile);
}
// Register Event Handlers
websocket.setEventHandler(new WebSocketEventHandler() {
@Override
public void onClose() {
websocket = null;
notifyClosed();
}
@Override
public void onMessage(WebSocketMessage message) {
processWebSocketMessage(message);
}
@Override
public void onOpen() {
connected = true;
}
@Override
public void onPing() {
// nothing to do
}
@Override
public void onPong() {
// nothing to do
}
});
websocket.connect();
} catch (WebSocketException exception) {
throw new IOException(exception);
} catch (Throwable exception) {
// Defensively catch any programming errors from the weberknecht library.
throw new IOException(exception);
}
}
public PubCommands getCommands() {
if (commands == null) {
commands = new PubCommands(this);
}
return commands;
}
public boolean isConnected() {
return websocket != null && connected;
}
public void removeConnectionListener(PubConnectionListener listener) {
connectionListeners.remove(listener);
}
protected void notifyClosed() {
for (PubConnectionListener listener : connectionListeners) {
listener.connectionClosed(this);
}
// Clean up the callbackMap on termination.
List<Callback> callbacks = new ArrayList<Callback>(callbackMap.values());
for (Callback callback : callbacks) {
try {
callback.handleResult(PubResult.createJsonErrorResult("connection termination"));
} catch (JSONException e) {
}
}
callbackMap.clear();
}
protected void processWebSocketMessage(WebSocketMessage message) {
try {
JSONObject object = new JSONObject(message.getText());
DartDebugCorePlugin.log("pub <== " + object);
if (object.has("id")) {
processResponse(object);
} else {
processNotification(object);
}
} catch (JSONException exception) {
DartDebugCorePlugin.logError("Could not process message " + message.getText(), exception);
}
}
protected void sendRequest(JSONObject request) throws IOException, JSONException {
sendRequest(request, null);
}
protected void sendRequest(JSONObject request, Callback callback) throws IOException,
JSONException {
if (!isConnected()) {
throw new IOException("connection terminated");
}
int id = 0;
try {
synchronized (this) {
id = getNextRequestId();
request.put("id", id);
if (callback != null) {
callbackMap.put(id, callback);
}
}
DartDebugCorePlugin.log("pub ==> " + request);
websocket.send(request.toString());
} catch (WebSocketException exception) {
if (callback != null) {
synchronized (this) {
callbackMap.remove(id);
}
}
throw new IOException(exception);
}
}
private int getNextRequestId() {
return ++requestId;
}
private void processNotification(JSONObject object) {
// TODO: pub does not yet send notifications
}
private void processResponse(JSONObject result) {
try {
int id = result.optInt("id", -1);
Callback callback;
synchronized (this) {
callback = callbackMap.remove(id);
}
if (callback != null) {
callback.handleResult(result);
} else if (result.has("error")) {
// If we get an error back, and nobody was listening for the result, then log it.
PubResult<?> pubResult = PubResult.createFrom(result);
DartDebugCorePlugin.logInfo("Error from command id " + id + ": " + pubResult.getError());
}
} catch (Throwable exception) {
DartDebugCorePlugin.logError(exception);
}
}
}