/** * 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.frontend.testing; import com.google.common.base.Preconditions; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Maps; import org.waveprotocol.box.common.DeltaSequence; import org.waveprotocol.box.common.IndexWave; import org.waveprotocol.box.common.comms.WaveClientRpc; import org.waveprotocol.box.server.common.CoreWaveletOperationSerializer; import org.waveprotocol.box.server.util.WaveletDataUtil; import org.waveprotocol.box.server.waveserver.WaveletProvider.SubmitRequestListener; import org.waveprotocol.wave.federation.Proto.ProtocolWaveletDelta; import org.waveprotocol.wave.federation.Proto.ProtocolWaveletOperation; import org.waveprotocol.wave.model.id.IdFilter; 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.operation.wave.TransformedWaveletDelta; import org.waveprotocol.wave.model.version.HashedVersion; import org.waveprotocol.wave.model.wave.ParticipantId; import org.waveprotocol.wave.model.wave.data.WaveletData; import java.util.Collection; import java.util.Map; import javax.annotation.Nullable; /** * A fake single-user wave server which only echoes back submitted deltas and * corresponding index wave deltas. * * @author mk.mateng@gmail.com (Michael Kuntzman) */ public class FakeWaveServer extends FakeClientFrontend { /** Fake application timestamp for confirming a successful submit. */ private static final long APP_TIMESTAMP = 0; /** Known wavelet states, excluding index wavelets. */ private final Map<WaveId, Map<WaveletId, WaveletData>> waves = Maps.newHashMap(); /** A history of submitted deltas, per wavelet. Does not store generated index deltas. */ private final ListMultimap<WaveletName, TransformedWaveletDelta> deltas = ArrayListMultimap.create(); /** The current versions of the user's wavelets, including index wavelets */ private final Map<WaveletName, HashedVersion> versions = Maps.newHashMap(); /** The user that is connected to this server */ private ParticipantId user = null; @Override public void openRequest(ParticipantId participant, WaveId waveId, IdFilter waveletIdFilter, Collection<WaveClientRpc.WaveletVersion> knownWavelets, OpenListener openListener) { if (user == null) { user = participant; } else { Preconditions.checkArgument(participant.equals(user), "Unexpected user"); } super.openRequest(participant, waveId, waveletIdFilter, knownWavelets, openListener); Map<WaveletId, WaveletData> wavelets = waves.get(waveId); if (wavelets != null) { // Send any deltas we have in this wave to the client, in the order we got // them. for (WaveletData wavelet : wavelets.values()) { WaveletName name = WaveletName.of(wavelet.getWaveId(), wavelet.getWaveletId()); waveletUpdate(wavelet, DeltaSequence.of(deltas.get(name))); } } } @Override public void submitRequest(ParticipantId loggedInUser, WaveletName waveletName, ProtocolWaveletDelta delta, @Nullable String channelId, SubmitRequestListener listener) { Preconditions.checkArgument( !IndexWave.isIndexWave(waveletName.waveId), "Cannot modify index wave"); super.submitRequest(loggedInUser, waveletName, delta, channelId, listener); Map<WaveletId, WaveletData> wavelets = waves.get(waveletName.waveId); if (wavelets == null) { wavelets = Maps.newHashMap(); waves.put(waveletName.waveId, wavelets); } WaveletData wavelet = wavelets.get(waveletName.waveletId); if (wavelet == null) { long dummyCreationTime = System.currentTimeMillis(); wavelet = WaveletDataUtil.createEmptyWavelet( waveletName, ParticipantId.ofUnsafe(delta.getAuthor()), HashedVersion.unsigned(0), dummyCreationTime); wavelets.put(waveletName.waveletId, wavelet); } // Add the delta to the history and update the wavelet's version. HashedVersion resultingVersion = updateAndGetVersion(waveletName, delta.getOperationCount()); TransformedWaveletDelta versionedDelta = CoreWaveletOperationSerializer.deserialize(delta, resultingVersion, APP_TIMESTAMP); deltas.put(waveletName, versionedDelta); // Confirm submit success. doSubmitSuccess(waveletName, resultingVersion, APP_TIMESTAMP); // Send an update echoing the submitted delta. Note: the document state is // ignored. waveletUpdate(wavelet, DeltaSequence.of(versionedDelta)); // Send a corresponding update of the index wave. doIndexUpdate(wavelet, delta); } /** * Generate and send an update of the user's index wave based on the specified delta. * * @param wavelet wavelet being changed * @param delta the delta on the wavelet */ private void doIndexUpdate(WaveletData wavelet, ProtocolWaveletDelta delta) { // If the wavelet cannot be indexed, then the delta doesn't affect the index wave. WaveletName waveletName = WaveletName.of(wavelet.getWaveId(), wavelet.getWaveletId()); if (!IndexWave.canBeIndexed(waveletName)) { return; } // TODO(Michael): Use IndexWave.createIndexDeltas instead of all this. // Go over the delta operations and extract only the add/remove participant ops that involve // our user. We do not update the index digests nor care about other users being added/removed. ProtocolWaveletDelta.Builder indexDelta = ProtocolWaveletDelta.newBuilder(); for (ProtocolWaveletOperation op : delta.getOperationList()) { boolean copyOp = false; if (op.hasAddParticipant()) { copyOp |= (new ParticipantId(op.getAddParticipant()).equals(user)); } if (op.hasRemoveParticipant()) { copyOp |= (new ParticipantId(op.getRemoveParticipant()).equals(user)); } if (copyOp) { indexDelta.addOperation(ProtocolWaveletOperation.newBuilder(op).build()); } } // If there is nothing to send, we're done. if (indexDelta.getOperationCount() == 0) { return; } // Find the index wavelet name and version. Update the version. WaveletName indexWaveletName = IndexWave.indexWaveletNameFor(wavelet.getWaveId()); HashedVersion targetVersion = versions.get(indexWaveletName); if (targetVersion == null) { targetVersion = HashedVersion.unsigned(0); } // Finish constructing the index wavelet delta and send it to the client. indexDelta.setAuthor(delta.getAuthor()); indexDelta.setHashedVersion(CoreWaveletOperationSerializer.serialize(targetVersion)); long dummyCreationTime = System.currentTimeMillis(); WaveletData fakeIndexWavelet = WaveletDataUtil.createEmptyWavelet( indexWaveletName, ParticipantId.ofUnsafe(delta.getAuthor()), targetVersion, dummyCreationTime); HashedVersion resultingVersion = updateAndGetVersion(indexWaveletName, indexDelta.getOperationCount()); waveletUpdate(fakeIndexWavelet, DeltaSequence.of( CoreWaveletOperationSerializer.deserialize(indexDelta.build(), resultingVersion, APP_TIMESTAMP))); } /** * Updates and returns the version of a given wavelet. * * @param waveletName of the wavelet whose version to update. * @param operationsCount applied to the wavelet. * @return the new hashed version of the wavelet. */ private HashedVersion updateAndGetVersion(WaveletName waveletName, int operationsCount) { // Get the current version. HashedVersion version = versions.get(waveletName); // Calculate the new version. if (version != null) { version = HashedVersion.unsigned(version.getVersion() + operationsCount); } else { version = HashedVersion.unsigned(operationsCount); } // Store and return the new version. versions.put(waveletName, version); return version; } }