/*
* Copyright 2011 Chad Retz
*
* 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 org.gwtnode.dev.debug;
import java.util.Stack;
import org.gwtnode.core.JavaScriptUtils;
import org.gwtnode.core.node.buffer.Buffer;
import org.gwtnode.core.node.event.ParameterlessEventHandler;
import org.gwtnode.core.node.event.StringOrBufferEventHandler;
import org.gwtnode.core.node.net.Socket;
import org.gwtnode.dev.debug.BufferStream.StreamIndexOutOfBoundsException;
import org.gwtnode.dev.debug.SessionHandler.InvokeResult;
import org.gwtnode.dev.debug.message.CheckVersionsMessage;
import org.gwtnode.dev.debug.message.FatalErrorMessage;
import org.gwtnode.dev.debug.message.FreeValueMessage;
import org.gwtnode.dev.debug.message.InvokeFromClientMessage;
import org.gwtnode.dev.debug.message.InvokeToClientMessage;
import org.gwtnode.dev.debug.message.LoadJsniMessage;
import org.gwtnode.dev.debug.message.LoadModuleMessage;
import org.gwtnode.dev.debug.message.Message;
import org.gwtnode.dev.debug.message.MessageType;
import org.gwtnode.dev.debug.message.ProtocolVersionMessage;
import org.gwtnode.dev.debug.message.QuitMessage;
import org.gwtnode.dev.debug.message.ReturnMessage;
/**
* Channel for communicating w/ GWT code server
*
* @author Chad Retz
*/
class HostChannel {
private static final int MINIMUM_PROTOCOL_VERSION = 2;
private static final int MAXIMUM_PROTOCOL_VERSION = 2;
private static final String HOSTED_HTML_VERSION = "2.1";
private final String moduleName;
private final String host;
private final int port;
private Socket socket;
private final BufferStream stream = new BufferStream();
private SessionHandler session;
private MessageCallback nextMessageCallback;
private final Stack<ReturnMessageCallback> returnMessageCallbacks = new Stack<ReturnMessageCallback>();
public HostChannel(String moduleName, String host, int port) {
this.moduleName = moduleName;
this.host = host;
this.port = port;
}
public void start(SessionHandler sess) {
this.session = sess;
if (socket != null) {
session.getLog().debug("Disconnecting previous channel");
disconnectFromHost();
}
socket = Socket.create();
socket.onData(new StringOrBufferEventHandler() {
@Override
protected void onEvent() {
try {
session.getLog().debug("Got buffer");
stream.append(getBuffer());
//grab a message
Message message;
do {
message = getNextMessage();
if (message != null) {
if (session.getLog().isDebugEnabled()) {
session.getLog().debug("Received message: %s", message.toString());
}
handleMessage(message);
}
} while (message != null);
session.getLog().debug("Still have %d bytes available",
stream.getBufferLength());
} catch (Exception e) {
session.getLog().error("Error: %s", JavaScriptUtils.
appendException(e, new StringBuilder()));
}
}
});
socket.connect(port, host, new ParameterlessEventHandler() {
@Override
public void onEvent() {
session.getLog().debug("Channel connection complete");
init();
}
});
}
private void init() {
session.getLog().debug("Initializing...");
sendMessage(new CheckVersionsMessage(MINIMUM_PROTOCOL_VERSION,
MAXIMUM_PROTOCOL_VERSION, HOSTED_HTML_VERSION), new MessageCallback() {
@Override
public void onMessage(Message message) {
if (message instanceof FatalErrorMessage) {
session.fatalError(((FatalErrorMessage) message).getError());
end();
} else if (message instanceof ProtocolVersionMessage) {
//yay!
//my session and my tab are one based on the current ms and a random number
String keyPrefix = Long.toHexString(System.currentTimeMillis()) +
"_" + Integer.toHexString((int) (Math.random() * Integer.MAX_VALUE));
sendMessage(new LoadModuleMessage(
//the URL is not really a URL, but rather my host and port
"gwt-node-debug://" + host + ":" + port,
"tab_" + keyPrefix,
"session_" + keyPrefix,
moduleName,
"gwt-node/0.0.1"));
} else {
throw new IllegalStateException("Unexpected message type: " + message.getType());
}
}
});
}
private void handleMessage(Message message) {
try {
if (nextMessageCallback != null) {
MessageCallback callback = nextMessageCallback;
nextMessageCallback = null;
callback.onMessage(message);
} else {
switch (message.getType()) {
case LOAD_JSNI:
session.loadJsni(((LoadJsniMessage) message).getJsCode());
break;
case FREE_VALUE:
session.freeValues(((FreeValueMessage) message).getRefIds());
break;
case INVOKE:
InvokeToClientMessage invokeMessage = (InvokeToClientMessage) message;
InvokeResult result = session.invoke(
invokeMessage.getMethodName(), invokeMessage.getThisValue(), invokeMessage.getArgValues());
sendMessage(new ReturnMessage(result.isException(), result.getValue()));
break;
case RETURN:
ReturnMessage returnMessage = (ReturnMessage) message;
if (returnMessageCallbacks.isEmpty()) {
session.getLog().error("Unexpected return message");
} else {
ReturnMessageCallback callback = returnMessageCallbacks.pop();
callback.onMessage(returnMessage);
}
break;
case FATAL_ERROR:
session.fatalError(((FatalErrorMessage) message).getError());
end();
case QUIT:
end();
break;
default:
throw new IllegalArgumentException("Unrecognized message type: " +
message.getType());
}
}
} catch (Exception e) {
session.getLog().error("Error handling message: %s",
JavaScriptUtils.appendException(e, new StringBuilder()));
}
}
private void end() {
if (socket != null) {
session.getLog().debug("Ending connection");
socket.end();
socket = null;
}
}
public void disconnectFromHost() {
if (socket != null) {
sendMessage(new QuitMessage());
end();
}
}
public void sendMessage(Message message) {
if (session.getLog().isDebugEnabled()) {
session.getLog().debug("Sending message: %s", message.toString());
}
Buffer buffer = message.toBuffer();
socket.write(buffer);
}
public void sendMessage(Message message, MessageCallback callback) {
if (session.getLog().isDebugEnabled()) {
session.getLog().debug("Sending message: %s", message.toString());
}
Buffer buffer = message.toBuffer();
socket.write(buffer);
nextMessageCallback = callback;
}
public void sendMessage(InvokeFromClientMessage message, ReturnMessageCallback callback) {
if (session.getLog().isDebugEnabled()) {
session.getLog().debug("Sending message: %s", message.toString());
}
Buffer buffer = message.toBuffer();
socket.write(buffer);
returnMessageCallbacks.push(callback);
}
private Message getNextMessage() {
try {
stream.beginTransaction();
MessageType type = MessageType.getMessageType(stream);
Message message = type.createMessage(stream, false);
stream.commitTransaction();
return message;
} catch (StreamIndexOutOfBoundsException e) {
stream.rollbackTransaction();
return null;
}
}
public SessionHandler getSession() {
return session;
}
public static interface MessageCallback {
void onMessage(Message message);
}
public static interface ReturnMessageCallback {
void onMessage(ReturnMessage message);
}
}