/** * Copyright 2008 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.concurrencycontrol.wave; import org.waveprotocol.wave.concurrencycontrol.channel.OperationChannel; import org.waveprotocol.wave.model.operation.wave.WaveletOperation; /** * Sucks operations out of an operation channel, and sends them to a sink for * application. * * @author zdwang@google.com (David Wang) */ public class OperationSucker implements OperationChannel.Listener { /** Channel from which this sucker sucks operations. */ private final OperationChannel opChannel; /** The sink to which each operation is passed after it is sucked. */ private final FlushingOperationSink<WaveletOperation> listener; /** * Flags if listener.flush(resumeCallback) returned false and * has not yet invoked resumeCallback. */ private boolean receptionIsPaused = false; /** Whether this sucker has been shut down. */ private boolean isShutdown = false; /** Callback for the flusher to use to resume the receive loop. */ private final Runnable resumeCallback = new Runnable() { public void run() { if (!receptionIsPaused) { throw new IllegalStateException( "Invalid attempt to resume wave channel reception when it's not paused. " + "Resume callback called back synchronously or repeatedly?"); } receptionIsPaused = false; receiveLoop(); } }; /** * Registers a listener on an operation channel that sucks out operations as * soon as they appear, sending them to a target sink. * * @param channel channel from which operations are to be pulled * @param sink sink to which operations are to be pushed */ public static void start(OperationChannel channel, FlushingOperationSink<WaveletOperation> sink) { channel.setListener(new OperationSucker(channel, sink)); } /** * This creates a concurrency control manager with an unconnected channel. * Call openWave() to connect to the wave server. * * @param channel channel from which to take ops * @param sink sink to which to send ops */ public OperationSucker(OperationChannel channel, FlushingOperationSink<WaveletOperation> sink) { this.opChannel = channel; this.listener = sink; } @Override public void onOperationReceived() { if (!receptionIsPaused) { receiveLoop(); } } /** * Shuts down this sucker. No more ops will be received. */ public void shutdown() { this.isShutdown = true; } /** * Pass any available transformed server operations from the op channel to the listener, * while the listener is willing to receive operations. If the receiver is unwilling, * we pause reception and await that the receiver resumes reception and reinvokes the * receive loop. * * Assumes reception is not paused on entry. */ private void receiveLoop() { if (isShutdown) { return; } WaveletOperation next = opChannel.peek(); while (next != null && !isShutdown) { // We flush the listener before every operation and specify what the next operation // will be, so the listener can choose to only flush the (editor of the) affected blip. // (Alternatively, we could tell the listener to flush everything before entering the // receive loop and then receive and pass on all available operations without further // flushes. That would be faster if, generally, there are more ops per batch than // blips in the wave. We have no evidence that that's the case.) receptionIsPaused = !listener.flush(next, resumeCallback); if (receptionIsPaused) { // The listener will call resume callback later, which clears receptionIsPaused // and runs the receive loop. We stop the receive loop until then. return; } // We take the next operation and pass it to the listener, if the // operation is still the same, that is, it has not been modified // by operation transformation against any client operations sent // to us from within the call to serverOperationListener.flush(). // Otherwise we cycle around and call flush again, just to be sure. WaveletOperation newNext = opChannel.peek(); if (next == newNext) { listener.consume(opChannel.receive()); if (!isShutdown) { next = opChannel.peek(); } } else { next = newNext; } } } }