/**
* 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.webclient.client;
import org.waveprotocol.box.common.comms.ProtocolWaveletUpdate;
import org.waveprotocol.box.common.comms.jso.ProtocolOpenRequestJsoImpl;
import org.waveprotocol.box.common.comms.jso.ProtocolSubmitRequestJsoImpl;
import org.waveprotocol.wave.model.id.IdFilter;
import org.waveprotocol.wave.model.id.InvalidIdException;
import org.waveprotocol.wave.model.id.ModernIdSerialiser;
import org.waveprotocol.wave.model.id.WaveId;
import org.waveprotocol.wave.model.id.WaveletId;
import org.waveprotocol.wave.model.id.WaveletName;
import org.waveprotocol.wave.model.util.CollectionUtils;
import java.util.Map;
/**
* Distributes the incoming update stream (from wave-in-a-box's client/server
* protocol) into per-wave streams.
*/
public final class RemoteViewServiceMultiplexer implements WaveWebSocketCallback {
/** Per-wave streams. */
private final Map<WaveId, WaveWebSocketCallback> streams = CollectionUtils.newHashMap();
//
// Workaround for issue 128.
// http://code.google.com/p/wave-protocol/issues/detail?id=128
//
// Filtering logic is as follows. Since not every update has a channel id, but
// all updates have a wavelet name, wave ids remain the primary key. This
// map's domain is a subset of streams' domain, and is monotonically set with
// the first channel id observed for an open wave. Only updates that have no
// channel id, or an equal channel id, are passed through to the stream.
// Closing the stream removes any known channel id from this map (this follows
// from the contraint that this maps's domain is a subset of streams' domain).
//
private final Map<WaveId, String> knownChannels = CollectionUtils.newHashMap();
/** Underlying socket. */
private final WaveWebSocketClient socket;
/** Identity, for authoring messages. */
private final String userId;
/**
* Creates a multiplexer.
*
* @param socket communication object
* @param userId identity of viewer
*/
public RemoteViewServiceMultiplexer(WaveWebSocketClient socket, String userId) {
this.socket = socket;
this.userId = userId;
// Note: Currently, the client's communication stack (websocket) is opened
// too early, before an identity is established. Once that is fixed, this
// object will be registered as a callback when the websocket is opened,
// rather than afterwards here.
socket.attachHandler(this);
}
/** Dispatches an update to the appropriate wave stream. */
@Override
public void onWaveletUpdate(ProtocolWaveletUpdate message) {
WaveletName wavelet = deserialize(message.getWaveletName());
// Route to the appropriate stream handler.
WaveWebSocketCallback stream = streams.get(wavelet.waveId);
if (stream != null) {
boolean drop;
String knownChannelId = knownChannels.get(wavelet.waveId);
if (knownChannelId != null) {
// Drop updates with known mismatched channel ids.
drop = message.hasChannelId() && !message.getChannelId().equals(knownChannelId);
} else {
if (message.hasChannelId()) {
knownChannels.put(wavelet.waveId, message.getChannelId());
}
drop = false;
}
if (!drop) {
stream.onWaveletUpdate(message);
}
} else {
// This is either a server error, or a message after a stream has been
// locally closed (there is no way to tell the server to stop sending
// updates).
}
}
/**
* Opens a wave stream.
*
* @param id wave to open
* @param stream handler to updates directed at that wave
*/
public void open(WaveId id, IdFilter filter, WaveWebSocketCallback stream) {
// Prepare to receive updates for the new stream.
streams.put(id, stream);
// Request those updates.
ProtocolOpenRequestJsoImpl request = ProtocolOpenRequestJsoImpl.create();
request.setWaveId(ModernIdSerialiser.INSTANCE.serialiseWaveId(id));
request.setParticipantId(userId);
for (String prefix : filter.getPrefixes()) {
request.addWaveletIdPrefix(prefix);
}
// Issue 161: http://code.google.com/p/wave-protocol/issues/detail?id=161
// The box protocol does not support explicit wavelet ids in the filter.
// As a workaround, include them in the prefix list.
for (WaveletId wid : filter.getIds()) {
request.addWaveletIdPrefix(wid.getId());
}
socket.open(request);
}
/**
* Closes a wave stream.
*
* @param id wave to close
* @param stream stream previously registered against that wave
*/
public void close(WaveId id, WaveWebSocketCallback stream) {
if (streams.get(id) == stream) {
streams.remove(id);
knownChannels.remove(id);
}
// Issue 117: the client server protocol does not support closing a wave stream.
}
/**
* Submits a delta.
*
* @param request delta to submit
* @param callback callback for submit response
*/
public void submit(ProtocolSubmitRequestJsoImpl request, SubmitResponseCallback callback) {
request.getDelta().setAuthor(userId);
socket.submit(request, callback);
}
public static WaveletName deserialize(String name) {
try {
return ModernIdSerialiser.INSTANCE.deserialiseWaveletName(name);
} catch (InvalidIdException e) {
throw new IllegalArgumentException(e);
}
}
public static String serialize(WaveletName name) {
return ModernIdSerialiser.INSTANCE.serialiseWaveletName(name);
}
}