Package org.waveprotocol.box.server.waveserver

Source Code of org.waveprotocol.box.server.waveserver.LocalWaveletContainerImpl

/**
* 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.box.server.waveserver;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;

import org.waveprotocol.box.server.common.CoreWaveletOperationSerializer;
import org.waveprotocol.box.server.persistence.PersistenceException;
import org.waveprotocol.wave.federation.Proto.ProtocolAppliedWaveletDelta;
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.RemoveParticipant;
import org.waveprotocol.wave.model.operation.wave.TransformedWaveletDelta;
import org.waveprotocol.wave.model.operation.wave.WaveletDelta;
import org.waveprotocol.wave.model.operation.wave.WaveletOperation;
import org.waveprotocol.wave.model.version.HashedVersion;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.util.logging.Log;

import java.util.concurrent.Executor;

/**
* 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);

  private static final Function<RemoveParticipant, ParticipantId> PARTICIPANT_REMOVED_BY =
      new Function<RemoveParticipant, ParticipantId>() {
        @Override
        public ParticipantId apply(RemoveParticipant op) {
          return op.getParticipantId();
        }
      };

  private static final Function<ParticipantId, String> DOMAIN_OF =
      new Function<ParticipantId, String>() {
        @Override
        public String apply(ParticipantId participant) {
          return participant.getDomain();
        }
      };

  private static Iterable<ParticipantId> participantsRemovedBy(Iterable<WaveletOperation> ops) {
    return Iterables.transform(Iterables.filter(ops, RemoveParticipant.class),
        PARTICIPANT_REMOVED_BY);
  }

  private static ImmutableSet<String> domainsOf(Iterable<ParticipantId> participants) {
    return ImmutableSet.copyOf(Iterables.transform(participants, DOMAIN_OF));
  }

  public LocalWaveletContainerImpl(WaveletName waveletName, WaveletNotificationSubscriber notifiee,
      ListenableFuture<? extends WaveletState> waveletStateFuture, String waveDomain,
      Executor storageContinuationExecutor) {
    super(waveletName, notifiee, waveletStateFuture, waveDomain, storageContinuationExecutor);
  }

  @Override
  public WaveletDeltaRecord submitRequest(WaveletName waveletName, ProtocolSignedDelta signedDelta)
      throws OperationException, InvalidProtocolBufferException, InvalidHashException,
      PersistenceException, WaveletStateException {
    awaitLoad();
    acquireWriteLock();
    try {
      checkStateOk();
      HashedVersion before = getCurrentVersion();
      WaveletDeltaRecord result = transformAndApplyLocalDelta(signedDelta);
      HashedVersion after = getCurrentVersion();
      // Only publish and persist the delta if it wasn't transformed away
      // (right now it never is since the current OT algorithm doesn't transform ops away)
      // and wasn't a duplicate of a previously applied delta.
      if (!after.equals(before)) {
        Preconditions.checkState(!result.isEmpty());
        Preconditions.checkState(result.getAppliedAtVersion().equals(before));
        ImmutableSet<String> domainsToNotify = domainsOf(Iterables.concat(
            accessSnapshot().getParticipants(),
            participantsRemovedBy(result.getTransformedDelta())));
        notifyOfDeltas(ImmutableList.of(result), domainsToNotify);
        // We always persist a local delta immediately after it's applied
        // and after it's broadcast on the wave bus and to remote servers.
        persist(result.getResultingVersion(), domainsToNotify);
      }
      return result;
    } 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 the transformed and applied 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
   */
  private WaveletDeltaRecord transformAndApplyLocalDelta(ProtocolSignedDelta signedDelta)
      throws OperationException, InvalidProtocolBufferException, InvalidHashException,
      PersistenceException {
    ProtocolWaveletDelta protocolDelta =
        ByteStringMessage.parseProtocolWaveletDelta(signedDelta.getDelta()).getMessage();

    Preconditions.checkArgument(protocolDelta.getOperationCount() > 0, "empty delta");

    WaveletDelta transformed = maybeTransformSubmittedDelta(
        CoreWaveletOperationSerializer.deserialize(protocolDelta));

    // TODO(ljvderijk): a Clock needs to be injected here (Issue 104)
    long applicationTimestamp = System.currentTimeMillis();

    HashedVersion currentVersion = getCurrentVersion();

    // This is always false right now because the current algorithm doesn't transform ops away.
    if (transformed.size() == 0) {
      Preconditions.checkState(currentVersion.getVersion() != 0,
          "currentVersion can not be 0 if delta was transformed");
      Preconditions.checkState(
          transformed.getTargetVersion().getVersion() <= currentVersion.getVersion());
      // The delta was transformed away. That's OK but we don't call either
      // applyWaveletOperations(), because that will throw IllegalArgumentException, or
      // commitAppliedDelta(), because empty deltas cannot be part of the delta history.
      TransformedWaveletDelta emptyDelta = new TransformedWaveletDelta(transformed.getAuthor(),
          transformed.getTargetVersion(), applicationTimestamp, transformed);
      return new WaveletDeltaRecord(transformed.getTargetVersion(), null, emptyDelta);
    }

    if (!transformed.getTargetVersion().equals(currentVersion)) {
      Preconditions.checkState(
          transformed.getTargetVersion().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 idempotent).
      ByteStringMessage<ProtocolAppliedWaveletDelta> existingDeltaBytes =
          lookupAppliedDelta(transformed.getTargetVersion());
      TransformedWaveletDelta dupDelta = lookupTransformedDelta(transformed.getTargetVersion());
      LOG.info("Duplicate delta " + dupDelta + " for wavelet " + getWaveletName());
      // TODO(anorth): Replace these comparisons with methods on delta classes.
      Preconditions.checkState(dupDelta.getAuthor().equals(transformed.getAuthor()),
          "Duplicate delta detected but mismatched author, expected %s found %s",
          transformed.getAuthor(), dupDelta.getAuthor());
      Preconditions.checkState(Iterables.elementsEqual(dupDelta, transformed),
          "Duplicate delta detected but mismatched ops, expected %s found %s",
          transformed, dupDelta);

      return new WaveletDeltaRecord(transformed.getTargetVersion(), existingDeltaBytes, dupDelta);
    }

    // Build the applied delta to commit
    ByteStringMessage<ProtocolAppliedWaveletDelta> appliedDelta =
        AppliedDeltaUtil.buildAppliedDelta(signedDelta, transformed.getTargetVersion(),
            transformed.size(), applicationTimestamp);

    return applyDelta(appliedDelta, transformed);
  }

  @Override
  public boolean isDeltaSigner(HashedVersion version, ByteString signerId) {
    ByteStringMessage<ProtocolAppliedWaveletDelta> appliedDelta =
        lookupAppliedDeltaByEndVersion(version);
    if (appliedDelta == null) {
      return false;
    }
    ProtocolSignedDelta signedDelta = appliedDelta.getMessage().getSignedOriginalDelta();
    for (ProtocolSignature signature : signedDelta.getSignatureList()) {
      if (signature.getSignerId().equals(signerId)) return true;
    }
    return false;
  }
}
TOP

Related Classes of org.waveprotocol.box.server.waveserver.LocalWaveletContainerImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.