/**
* Copyright 2010 Google Inc.
*
* 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.waveprotocol.box.server.rpc;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.protobuf.Message;
import org.waveprotocol.box.server.rpc.ProtoSerializer.SerializationException;
import org.waveprotocol.wave.communication.gson.GsonException;
import org.waveprotocol.wave.communication.gson.GsonSerializable;
import org.waveprotocol.wave.util.logging.Log;
import java.io.IOException;
/**
* A channel abstraction for websocket, for sending and receiving strings.
*/
public abstract class WebSocketChannel extends MessageExpectingChannel {
private static final Log LOG = Log.get(WebSocketChannel.class);
/**
* Envelope for delivering arbitrary messages. Each envelope has a sequence
* number and a message.
* <p>
* Note that this message can not be described by a protobuf, because it
* contains an arbitrary protobuf, which breaks the protobuf typing rules.
*/
private static class MessageWrapper {
private final static JsonParser parser = new JsonParser();
final int sequenceNumber;
final String messageType;
final JsonElement message;
public MessageWrapper(int sequenceNumber, String messageType, JsonElement message) {
this.sequenceNumber = sequenceNumber;
this.messageType = messageType;
this.message = message;
}
public static MessageWrapper deserialize(Gson gson, String data) {
JsonElement e = parser.parse(data);
JsonObject obj = e.getAsJsonObject();
String type = obj.get("messageType").getAsString();
int seqno = obj.get("sequenceNumber").getAsInt();
JsonElement message = obj.get("message");
return new MessageWrapper(seqno, type, message);
}
public static String serialize(String type,int seqno, JsonElement message) {
JsonObject o = new JsonObject();
o.add("messageType", new JsonPrimitive(type));
o.add("sequenceNumber", new JsonPrimitive(seqno));
o.add("message", message);
return o.toString();
}
}
private final ProtoCallback callback;
private final Gson gson = new Gson();
private final ProtoSerializer serializer;
/**
* Constructs a new WebSocketChannel, using the callback to handle any
* incoming messages.
*
* @param callback a protocallback to be called when data arrives on this
* channel
*/
public WebSocketChannel(ProtoCallback callback) {
this.callback = callback;
// The ProtoSerializer could really be singleton.
// TODO: Figure out a way to inject a singleton instance using Guice
this.serializer = new ProtoSerializer();
}
public void handleMessageString(String data) {
LOG.fine("received JSON message " + data);
Message message;
MessageWrapper wrapper = MessageWrapper.deserialize(gson, data);
try {
message = serializer.fromJson(wrapper.message, wrapper.messageType);
} catch (SerializationException e) {
LOG.warning("message handling error", e);
e.printStackTrace();
return;
}
callback.message(wrapper.sequenceNumber, message);
}
static <T extends GsonSerializable> T load(JsonElement payload, T x, Gson gson) {
try {
x.fromGson(payload, gson, null);
return x;
} catch (GsonException e) {
LOG.warning("JSON load error", e);
e.printStackTrace();
return null;
}
}
/**
* Sends a message on the socket.
*
* @param data message to send
* @throws IOException if the communication fails
*/
protected abstract void sendMessageString(String data) throws IOException;
@Override
public void sendMessage(int sequenceNo, Message message) {
JsonElement json;
try {
json = serializer.toJson(message);
} catch (SerializationException e) {
LOG.warning("Failed to JSONify proto message", e);
return;
}
String type = message.getDescriptorForType().getName();
String str = MessageWrapper.serialize(type, sequenceNo, json);
try {
sendMessageString(str);
LOG.fine("sent JSON message over websocket, sequence number " + sequenceNo
+ ", message " + message);
} catch (IOException e) {
// TODO(anorth): This failure should be communicated to the caller
// so it can attempt retransmission.
LOG.warning("Failed to transmit message on socket, sequence number " + sequenceNo
+ ", message " + message, e);
return;
}
}
}