/**
* 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.examples.fedone.waveserver;
import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.waveprotocol.wave.examples.fedone.common.HashedVersion;
import org.waveprotocol.wave.examples.fedone.common.WaveletOperationSerializer;
import static org.waveprotocol.wave.examples.fedone.common.WaveletOperationSerializer.serialize;
import org.waveprotocol.wave.examples.fedone.util.Log;
import org.waveprotocol.wave.federation.Proto.ProtocolAppliedWaveletDelta;
import org.waveprotocol.wave.federation.Proto.ProtocolHashedVersion;
import org.waveprotocol.wave.federation.Proto.ProtocolSignature;
import org.waveprotocol.wave.federation.Proto.ProtocolSignedDelta;
import org.waveprotocol.wave.federation.Proto.ProtocolWaveletDelta;
import org.waveprotocol.wave.model.id.WaveletName;
import org.waveprotocol.wave.model.operation.OperationException;
import org.waveprotocol.wave.model.operation.wave.WaveletDelta;
import org.waveprotocol.wave.model.operation.wave.WaveletOperation;
import org.waveprotocol.wave.model.util.Pair;
import java.util.List;
/**
* A local wavelet may be updated by submits. The local wavelet will perform
* operational transformation on the submitted delta and assign it the latest
* version of the wavelet.
*/
class LocalWaveletContainerImpl extends WaveletContainerImpl
implements LocalWaveletContainer {
private static final Log LOG = Log.get(LocalWaveletContainerImpl.class);
/**
* Associates a delta (represented by the hashed version of the wavelet state after its
* application) with its signers (represented by their signer id)
*/
private final Multimap<ProtocolHashedVersion, ByteString> deltaSigners =
ArrayListMultimap.create();
public LocalWaveletContainerImpl(WaveletName waveletName) {
super(waveletName);
}
@Override
public DeltaApplicationResult submitRequest(WaveletName waveletName,
ProtocolSignedDelta signedDelta) throws OperationException,
InvalidProtocolBufferException, InvalidHashException, EmptyDeltaException {
acquireWriteLock();
try {
return transformAndApplyLocalDelta(signedDelta);
} finally {
releaseWriteLock();
}
}
/**
* Apply a signed delta to a local wavelet. This assumes the caller has
* validated that the delta is at the correct version and can be applied to
* the wavelet. Must be called with writelock held.
*
* @param signedDelta the delta that is to be applied to wavelet.
* @return transformed operations are applied to this delta.
* @throws OperationException if an error occurs during transformation or
* application
* @throws InvalidProtocolBufferException if the signed delta did not contain a valid delta
* @throws InvalidHashException if delta hash sanity checks fail
*
* @throws OperationException
*/
private DeltaApplicationResult transformAndApplyLocalDelta(ProtocolSignedDelta signedDelta)
throws OperationException, InvalidProtocolBufferException, EmptyDeltaException,
InvalidHashException {
ByteStringMessage<ProtocolWaveletDelta> protocolDelta = ByteStringMessage.from(
ProtocolWaveletDelta.getDefaultInstance(), signedDelta.getDelta());
Pair<WaveletDelta, HashedVersion> deltaAndVersion =
WaveletOperationSerializer.deserialize(protocolDelta.getMessage());
if (deltaAndVersion.first.getOperations().isEmpty()) {
LOG.warning("No operations to apply at version " + deltaAndVersion.second);
throw new EmptyDeltaException();
}
VersionedWaveletDelta transformed =
maybeTransformSubmittedDelta(deltaAndVersion.first, deltaAndVersion.second);
// This is always false right now because the current algorithm doesn't transform ops away.
if (transformed.delta.getOperations().isEmpty()) {
Preconditions.checkState(transformed.version.getVersion() <= currentVersion.getVersion());
// The delta was transformed away. That's OK but we don't call either
// applyWaveletOperations(), because that will throw EmptyDeltaException, or
// commitAppliedDelta(), because empty deltas cannot be part of the delta history.
return new DeltaApplicationResult(buildAppliedDelta(signedDelta, transformed),
WaveletOperationSerializer.serialize(transformed.delta, transformed.version),
WaveletOperationSerializer.serialize(transformed.version));
}
if (!transformed.version.equals(currentVersion)) {
Preconditions.checkState(transformed.version.getVersion() < currentVersion.getVersion());
// The delta was a duplicate of an existing server delta.
// We duplicate-eliminate it (don't apply it to the wavelet state and don't store it in
// the delta history) and return the server delta which it was a duplicate of
// (so delta submission becomes idem-potent).
ByteStringMessage<ProtocolAppliedWaveletDelta> appliedDelta =
lookupAppliedDelta(transformed.version);
// TODO: look this up (in appliedDeltas or currentVersion) rather than compute it?
HashedVersion hashedVersionAfterApplication = HASHED_HISTORY_VERSION_FACTORY.create(
appliedDelta.getByteArray(), transformed.version, transformed.delta.getOperations().size());
return new DeltaApplicationResult(appliedDelta,
WaveletOperationSerializer.serialize(transformed.delta, transformed.version),
WaveletOperationSerializer.serialize(hashedVersionAfterApplication));
}
// If any of the operations fail to apply, the wavelet data will be returned to its original
// state and an OperationException thrown
applyWaveletOperations(transformed.delta.getOperations());
// Build the applied delta to commit
ByteStringMessage<ProtocolAppliedWaveletDelta> appliedDelta =
buildAppliedDelta(signedDelta, transformed);
DeltaApplicationResult applicationResult = commitAppliedDelta(appliedDelta,
transformed.delta);
// Associate this hashed version with its signers.
for (ProtocolSignature signature : signedDelta.getSignatureList()) {
deltaSigners.put(serialize(HashedVersion.getHashedVersionAfter(appliedDelta)),
signature.getSignerId());
}
return applicationResult;
}
private static ByteStringMessage<ProtocolAppliedWaveletDelta> buildAppliedDelta(
ProtocolSignedDelta signedDelta, VersionedWaveletDelta transformed) {
ProtocolAppliedWaveletDelta.Builder appliedDeltaBuilder =
ProtocolAppliedWaveletDelta.newBuilder()
.setSignedOriginalDelta(signedDelta)
.setOperationsApplied(transformed.delta.getOperations().size())
.setApplicationTimestamp(System.currentTimeMillis());
// TODO: re-enable this condition for version 0.3 of the spec
if (/*opsWereTransformed*/ true) {
// This is set to indicate the head version of the wavelet was different to the intended
// version of the wavelet (so the hash will have changed)
appliedDeltaBuilder.setHashedVersionAppliedAt(
WaveletOperationSerializer.serialize(transformed.version));
}
return ByteStringMessage.fromMessage(appliedDeltaBuilder.build());
}
@Override
public boolean isDeltaSigner(ProtocolHashedVersion version, ByteString signerId) {
return deltaSigners.get(version).contains(signerId);
}
}