/**
* Copyright 2009 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.concurrencycontrol.common.ChannelException;
import org.waveprotocol.wave.concurrencycontrol.wave.CcBasedWaveViewImpl.CcDocumentFactory;
import org.waveprotocol.wave.model.document.ObservableDocument;
import org.waveprotocol.wave.model.id.WaveId;
import org.waveprotocol.wave.model.id.WaveletId;
import org.waveprotocol.wave.model.operation.OperationException;
import org.waveprotocol.wave.model.operation.SilentOperationSink;
import org.waveprotocol.wave.model.operation.wave.WaveletBlipOperation;
import org.waveprotocol.wave.model.operation.wave.WaveletOperation;
import org.waveprotocol.wave.model.operation.wave.WaveletOperationContext;
import org.waveprotocol.wave.model.util.CopyOnWriteSet;
import org.waveprotocol.wave.model.util.Preconditions;
import org.waveprotocol.wave.model.version.HashedVersion;
import org.waveprotocol.wave.model.wave.Blip;
import org.waveprotocol.wave.model.wave.ObservableWavelet;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.model.wave.ParticipationHelper;
import org.waveprotocol.wave.model.wave.WaveletListener;
import org.waveprotocol.wave.model.wave.data.ObservableWaveletData;
import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet;
import java.util.Set;
/**
* A Wavelet that is based on Cc.
*
*/
public final class CcBasedWavelet implements ObservableWavelet {
/**
* Factory for {@link CcBasedWavelet}s in the same wave.
*/
static class Factory {
private final WaveId waveId;
private final WaveletOperationContext.Factory contextFactory;
private final ParticipationHelper participationHelper;
private final CcDocumentFactory<?> docFactory;
private final FailureHandler failureHandler;
private final DuplexOpSinkFactory opTaps;
Factory(WaveId waveId, WaveletOperationContext.Factory contextFactory,
ParticipationHelper participationHelper, CcDocumentFactory<?> docFactory,
FailureHandler failureHandler, DuplexOpSinkFactory opTaps) {
this.waveId = waveId;
this.contextFactory = contextFactory;
this.participationHelper = participationHelper;
this.docFactory = docFactory;
this.failureHandler = failureHandler;
this.opTaps = opTaps;
}
/**
* Places a concurrency-control driver and target on top of the new
* operation channel. All wavelet model objects are created through this
* method.
*
* @param target target to be controller by operations
* @param channel channel for operation communication
* @return new wavelet
*/
CcBasedWavelet create(OperationChannel channel, ObservableWaveletData target) {
return new CcBasedWavelet(waveId, contextFactory, participationHelper, channel, target,
docFactory, failureHandler, opTaps);
}
}
/**
* Handler for failure of the concurrency control channel supporting a
* wavelet.
*
* When a wavelet fails the handler must ensure the wavelet is not further
* mutated either through application of remote operations or local mutation
* methods. Side-effect-free queries remain usable.
*/
interface FailureHandler {
/**
* Called when an operation fails to apply to the wavelet.
*
* @param failure the exception raised by the wavelet/channel.
*/
void onWaveletFailed(OperationException failure);
}
/**
* An applier can flush and can consume operations against a target.
*/
private class OperationApplier implements FlushingOperationSink<WaveletOperation> {
/** Target being operated. */
private final ObservableWaveletData target;
private final WaveletId waveletId;
/** Attachments hack. */
private SilentOperationSink<WaveletOperation> extraHandler;
/** Just for debugging, so we know what ops have been applied before any death. */
private WaveletOperation debugLastSuccessfulOp = null;
OperationApplier(ObservableWaveletData target, WaveletId waveletId) {
this.target = target;
this.waveletId = waveletId;
}
@Override
public boolean flush(WaveletOperation operation, Runnable resume) {
Preconditions.checkState(!failed, "CcBasedWavelet operation applier flushed after failure");
if (operation instanceof WaveletBlipOperation) {
WaveletBlipOperation waveBlipOp = (WaveletBlipOperation) operation;
CcDocument doc = docFactory.get(waveletId, waveBlipOp.getBlipId());
if (doc != null) {
return doc.flush(resume);
}
}
return true;
}
/**
* HACK(user):
* Inserts an extra operation handler. This is only required for
* attachment handling.
*
* @param extraHandler
*/
void register(SilentOperationSink<WaveletOperation> extraHandler) {
this.extraHandler = extraHandler;
}
@Override
public void consume(WaveletOperation waveOp) {
Preconditions.checkState(!failed,
"CcBasedWavelet operation applier received op after failure");
try {
fireOnOpBegin(waveOp.getContext(), waveOp.isWorthyOfAttribution());
waveOp.apply(target);
// Hack for attachments
if (extraHandler != null) {
extraHandler.consume(waveOp);
}
// attachmentsManager.getServerOperationListener().consume(waveOp);
} catch (OperationException e) {
// Fail this wavelet permanently
fail(new OperationException("Operation failed; no recovery possible: " + e.getMessage()
+ "\n[debugLastSuccessfulOp:" + debugLastSuccessfulOp + "]"
+ "\n[waveOp:" + waveOp + "]"
+ "\n[target:" + target + "]", e));
} finally {
fireOnOpEnd();
}
debugLastSuccessfulOp = waveOp;
}
}
/**
* Adapts an operation channel, making it look like an operation sink.
* The only reason a channel is not already a sink is because it has a more
* general acceptor that takes a varargs parameter.
*/
private static class ChannelAdapter implements SilentOperationSink<WaveletOperation> {
private final OperationChannel target;
public ChannelAdapter(OperationChannel target) {
this.target = target;
}
@Override
public void consume(WaveletOperation op) {
try {
target.send(op);
} catch (ChannelException e) {
throw new RuntimeException("Send failed, channel is broken", e);
}
}
}
private final OperationApplier applier;
private final ChannelAdapter remote;
private final OpBasedWavelet wavelet;
private final CcDocumentFactory<?> docFactory;
private final CopyOnWriteSet<CcBasedWaveletListener> ccBasedWaveListeners =
CopyOnWriteSet.create();
private final FailureHandler failureHandler;
private final OperationChannel channel;
private final DuplexOpSink opTap;
private final OperationSucker driver;
private boolean failed = false;
CcBasedWavelet(WaveId waveId, WaveletOperationContext.Factory contextFactory,
ParticipationHelper participationHelper, OperationChannel channel,
ObservableWaveletData target, CcDocumentFactory<?> docFactory,
FailureHandler failureHandler, DuplexOpSinkFactory opTaps) {
this.docFactory = docFactory;
this.channel = channel;
this.failureHandler = failureHandler;
Preconditions.checkNotNull(opTaps, "OpTaps cannot be null");
// Sink through which all operations are sent to remote state
remote = new ChannelAdapter(channel);
// Sink through which all operations are applied to local state
applier = new OperationApplier(target, target.getWaveletId());
opTap = opTaps.create(target.getWaveletId(), applier, remote);
// Create operation adapter, which translates semantic methods into
// operations, applying them to the target as well as sending them down
// the operation channel. Local operation failure fails this wavelet
// permanently.
wavelet = new OpBasedWavelet(waveId, target, contextFactory, participationHelper,
opTap.incoming(), opTap.outgoing());
// Create operation sucker, which sucks operations out of the channel and
// applies them to the CC target.
driver = new OperationSucker(channel, opTap.incoming());
channel.setListener(driver);
}
/**
* Add an OpBasedWaveletListener.
*/
public void addCcBasedWaveletListener(
org.waveprotocol.wave.concurrencycontrol.wave.CcBasedWaveletListener listener) {
ccBasedWaveListeners.add(listener);
}
/**
* Remove an OpBasedWaveletListener.
*/
public void removeCcBasedWaveletListener(CcBasedWaveletListener listener) {
ccBasedWaveListeners.remove(listener);
}
/**
* @return the debug string of cc stack
*/
public String getCcDebugString() {
return channel.getDebugString();
}
/**
* @return the underlying wavelet.
*/
public OpBasedWavelet getOpBasedWavelet() {
return wavelet;
}
private void fireOnOpBegin(WaveletOperationContext context, boolean isWorthyOfContribution) {
for (CcBasedWaveletListener l : ccBasedWaveListeners) {
l.onOpBegin(context.getCreator(), context.getTimestamp(), isWorthyOfContribution);
}
}
private void fireOnOpEnd() {
for (CcBasedWaveletListener l : ccBasedWaveListeners) {
l.onOpEnd();
}
}
/**
* Fails this wavelet, removing listeners and notifying the wavelet's failure
* handler that this wavelet is unusable.
*
* @param failure exception causing the failure
*/
private void fail(OperationException failure) {
if (!failed) {
failed = true;
driver.shutdown();
ccBasedWaveListeners.clear();
// Currently this will cause the wave to disconnect.
failureHandler.onWaveletFailed(failure);
}
}
/**
* @throws IllegalStateException if this wavelet has failed.
*/
private void checkNotFailed() {
if (failed) {
throw new IllegalStateException("CcBasedWavelet used after failure");
}
}
//
// Delegates to OpBasedWavelet
//
@Override
public void addParticipant(ParticipantId participant) {
checkNotFailed();
wavelet.addParticipant(participant);
}
@Override
public Iterable<? extends Blip> getBlips() {
return wavelet.getBlips();
}
@Override
public Blip getBlip(String blipId) {
return wavelet.getBlip(blipId);
}
@Override
public Blip createBlip(String id) {
return wavelet.createBlip(id);
}
@Override
public long getCreationTime() {
return wavelet.getCreationTime();
}
@Override
public ParticipantId getCreatorId() {
return wavelet.getCreatorId();
}
@Override
public ObservableDocument getDocument(String documentName) {
return wavelet.getDocument(documentName);
}
@Override
public Set<String> getDocumentIds() {
return wavelet.getDocumentIds();
}
@Override
public WaveletId getId() {
return wavelet.getId();
}
@Override
public long getLastModifiedTime() {
return wavelet.getLastModifiedTime();
}
@Override
public Set<ParticipantId> getParticipantIds() {
return wavelet.getParticipantIds();
}
@Override
public long getVersion() {
return wavelet.getVersion();
}
@Override
public HashedVersion getHashedVersion() {
return wavelet.getHashedVersion();
}
@Deprecated
@Override
public WaveId getWaveId() {
return wavelet.getWaveId();
}
@Override
public void addListener(WaveletListener listener) {
wavelet.addListener(listener);
}
@Override
public void removeListener(WaveletListener listener) {
wavelet.removeListener(listener);
}
@Override
public void removeParticipant(ParticipantId participant) {
checkNotFailed();
wavelet.removeParticipant(participant);
}
@Override
public String toString() {
return wavelet.toString();
}
}