/** * 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; } } }