/** * 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.wave.examples.fedone.rpc; import java.io.IOException; import com.google.gson.Gson; import com.google.gson.JsonParseException; import com.google.protobuf.JsonFormat; import com.google.protobuf.Message; import org.waveprotocol.wave.examples.fedone.util.Log; import org.eclipse.jetty.websocket.WebSocket; /** * 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); private static final int VERSION = 0; private final ProtoCallback callback; private Gson gson = new Gson(); /** * 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; } /** * A simple message wrapper that bundles a json string with a version, * sequence number, and type information. */ private static class MessageWrapper { private int version; private long sequenceNumber; private String messageType; private String messageJson; MessageWrapper() { // no-args constructor } MessageWrapper(int version, long sequenceNumber, String messageType, String messageJson) { this.version = version; this.sequenceNumber = sequenceNumber; this.messageType = messageType; this.messageJson = messageJson; } } /** * Convert the given string into a Message object and pass it to the proto * callback. * * @param data A json-encoded MessageWrapper object. */ public void handleMessageString(String data) { MessageWrapper wrapper = null; try { wrapper = gson.fromJson(data, MessageWrapper.class); } catch (JsonParseException jpe) { LOG.info("Unable to parse JSON: " + jpe.getMessage()); throw new IllegalArgumentException(jpe); } if (wrapper.version != VERSION) { LOG.info("Bad message version number: " + wrapper.version); throw new IllegalArgumentException("Bad version number: " + wrapper.version); } Message prototype = getMessagePrototype(wrapper.messageType); if (prototype == null) { LOG.info("Received misunderstood message (??? " + wrapper.messageType + " ???, seq " + wrapper.sequenceNumber + ") from: " + this); callback.unknown(wrapper.sequenceNumber, wrapper.messageType, wrapper.messageJson); } else { Message.Builder builder = prototype.newBuilderForType(); try { JsonFormat.merge(wrapper.messageJson, builder); callback.message(wrapper.sequenceNumber, builder.build()); } catch (JsonFormat.ParseException pe) { LOG.info("Unable to parse message (" + wrapper.messageType + ", seq " + wrapper.sequenceNumber + ") from: " + this + " -- " + wrapper.messageJson); callback.unknown(wrapper.sequenceNumber, wrapper.messageType, wrapper.messageJson); } } } /** * Send the given data String * * @param data */ protected abstract void sendMessageString(String data); /** * Send the given message across the connection along with the sequence number. * * @param sequenceNo * @param message */ public void sendMessage(long sequenceNo, Message message) { sendMessageString(gson.toJson(new MessageWrapper( VERSION, sequenceNo, message.getDescriptorForType().getFullName(), JsonFormat.printToString(message)))); } }