Package com.google.walkaround.wave.server

Source Code of com.google.walkaround.wave.server.WaveLoader$LoadedWave

/*
* Copyright 2011 Google Inc. All Rights Reserved.
*
* 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 com.google.walkaround.wave.server;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.google.walkaround.proto.WalkaroundWaveletSnapshot;
import com.google.walkaround.proto.WaveletDiffSnapshot;
import com.google.walkaround.slob.server.AccessDeniedException;
import com.google.walkaround.slob.server.SlobNotFoundException;
import com.google.walkaround.slob.server.SlobStore;
import com.google.walkaround.slob.server.SlobStore.ConnectResult;
import com.google.walkaround.slob.server.SlobStore.HistoryResult;
import com.google.walkaround.slob.shared.ChangeData;
import com.google.walkaround.slob.shared.ClientId;
import com.google.walkaround.slob.shared.MessageException;
import com.google.walkaround.slob.shared.SlobId;
import com.google.walkaround.util.shared.Assert;
import com.google.walkaround.wave.server.conv.ConvStore;
import com.google.walkaround.wave.server.model.ServerMessageSerializer;
import com.google.walkaround.wave.server.udw.UdwStore;
import com.google.walkaround.wave.shared.IdHack;
import com.google.walkaround.wave.shared.WaveSerializer;

import org.waveprotocol.wave.model.document.operation.DocInitialization;
import org.waveprotocol.wave.model.document.operation.automaton.DocumentSchema;
import org.waveprotocol.wave.model.id.WaveletId;
import org.waveprotocol.wave.model.id.WaveletName;
import org.waveprotocol.wave.model.supplement.PrimitiveSupplement;
import org.waveprotocol.wave.model.supplement.WaveletBasedSupplement;
import org.waveprotocol.wave.model.util.CollectionUtils;
import org.waveprotocol.wave.model.util.Pair;
import org.waveprotocol.wave.model.util.StringMap;
import org.waveprotocol.wave.model.wave.data.DocumentFactory;
import org.waveprotocol.wave.model.wave.data.impl.ObservablePluggableMutableDocument;
import org.waveprotocol.wave.model.wave.data.impl.WaveletDataImpl;
import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet;

import javax.annotation.Nullable;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;

/**
* Handles loading waves.
*
* @author danilatos@google.com (Daniel Danilatos)
* @author ohler@google.com (Christian Ohler)
*/
public class WaveLoader {

  public static class LoadedUdw {
    private final SlobId objectId;
    private final ConnectResult connectResult;
    private final WalkaroundWaveletSnapshot snapshot;

    public LoadedUdw(SlobId objectId,
        ConnectResult connectResult,
        WalkaroundWaveletSnapshot snapshot) {
      Preconditions.checkNotNull(objectId, "Null objectId");
      Preconditions.checkNotNull(connectResult, "Null connectResult");
      Preconditions.checkNotNull(snapshot, "Null snapshot");
      this.objectId = objectId;
      this.connectResult = connectResult;
      this.snapshot = snapshot;
    }

    public SlobId getObjectId() {
      return objectId;
    }

    public ConnectResult getConnectResult() {
      return connectResult;
    }

    public WalkaroundWaveletSnapshot getSnapshot() {
      return snapshot;
    }

    @Override public String toString() {
      return "LoadedUdw(" + objectId + ", " + connectResult + ")";
    }
  }

  public static class LoadedWave {
    private final SlobId convObjectId;
    @Nullable private final ConnectResult convConnectResult;
    private final WaveletDiffSnapshot convSnapshotWithDiffs;
    @Nullable private final LoadedUdw udw;

    public LoadedWave(SlobId convObjectId,
        @Nullable ConnectResult convConnectResult,
        WaveletDiffSnapshot convSnapshotWithDiffs,
        @Nullable LoadedUdw udw) {
      this.convObjectId = checkNotNull(convObjectId, "Null convObjectId");
      this.convConnectResult = convConnectResult;
      this.convSnapshotWithDiffs =
          checkNotNull(convSnapshotWithDiffs, "Null convSnapshotWithDiffs");
      this.udw = udw;
    }

    public SlobId getConvObjectId() {
      return convObjectId;
    }

    @Nullable public ConnectResult getConvConnectResult() {
      return convConnectResult;
    }

    public WaveletDiffSnapshot getConvSnapshotWithDiffs() {
      return convSnapshotWithDiffs;
    }

    @Nullable public LoadedUdw getUdw() {
      return udw;
    }

    @Override public String toString() {
      return getClass().getSimpleName() + "("
          + convObjectId + ", "
          + convConnectResult + ", "
          + convSnapshotWithDiffs + ", "
          + udw
          + ")";
    }
  }

  private static final Logger log = Logger.getLogger(WaveLoader.class.getName());

  private final WaveletCreator waveletCreator;
  private final SlobStore convStore;
  private final SlobStore udwStore;
  private final boolean enableUdw;
  private final boolean enableDiffOnOpen;
  private final WaveSerializer serializer;

  @Inject
  public WaveLoader(WaveletCreator waveletCreator,
      @Flag(FlagName.ENABLE_UDW) boolean enableUdw,
      @Flag(FlagName.ENABLE_DIFF_ON_OPEN) boolean enableDiffOnOpen,
      @ConvStore SlobStore convStore,
      @UdwStore SlobStore udwStore) {
    this.waveletCreator = waveletCreator;
    this.convStore = convStore;
    this.udwStore = udwStore;
    this.enableUdw = enableUdw;
    this.enableDiffOnOpen = enableDiffOnOpen;
    serializer = new WaveSerializer(new ServerMessageSerializer(),
        new DocumentFactory<ObservablePluggableMutableDocument>() {
      @Override public ObservablePluggableMutableDocument create(
          WaveletId waveletId, String docId, DocInitialization content) {
        return new ObservablePluggableMutableDocument(
            DocumentSchema.NO_SCHEMA_CONSTRAINTS, content);
      }
    });
  }

  public LoadedWave loadStaticAtVersion(SlobId convObjectId, @Nullable Long version)
      throws IOException, AccessDeniedException, SlobNotFoundException {
    Preconditions.checkNotNull(convObjectId, "Null convObjectId");
    String rawConvSnapshot = convStore.loadAtVersion(convObjectId, version);
    WaveletName convWaveletName = IdHack.convWaveletNameFromConvObjectId(convObjectId);
    WaveletDataImpl convWavelet = deserializeWavelet(convWaveletName, rawConvSnapshot);
    // TODO(ohler): Determine if it's better UX if we load the UDW here as well.
    return waveWithoutUdw(convObjectId, null, convWavelet);
  }

  public LoadedWave load(SlobId convObjectId, ClientId clientId)
      throws IOException, AccessDeniedException, SlobNotFoundException {
    Preconditions.checkNotNull(convObjectId, "Null convObjectId");
    Preconditions.checkNotNull(clientId, "Null clientId");

    Pair<ConnectResult, String> convPair = convStore.connect(convObjectId, clientId);
    ConnectResult convResult = convPair.getFirst();
    String rawConvSnapshot = convPair.getSecond();
    log(convObjectId, convResult);

    WaveletName convWaveletName = IdHack.convWaveletNameFromConvObjectId(convObjectId);

    // The most recent version of wavelet to get list of documents from.
    WaveletDataImpl convWavelet = deserializeWavelet(convWaveletName, rawConvSnapshot);

    long convVersion = convResult.getVersion();
    Assert.check(convVersion == convWavelet.getVersion(),
        "ConnectResult revision %s does not match wavelet version %s",
        convVersion, convWavelet.getVersion());

    if (!enableUdw) {
      return waveWithoutUdw(convObjectId, convResult, convWavelet);
    } else {
      // Now we go and load some of the history in order to render diffs.
      // For fully read or unread blips, we don't need to load any history.
      // So the approach here is to find all the blips that are partially
      // read, and get the smallest version.

      // TODO(ohler): This should be getOrCreateAndConnect(), so that we can avoid
      // reconstructing the state of the wavelet that we just created.  But
      // snapshot caching would help with this as well, so we should probably do
      // that first.
      SlobId udwId = waveletCreator.getOrCreateUdw(convObjectId);
      Pair<ConnectResult, String> udwPair;
      try {
        udwPair = udwStore.connect(udwId, clientId);
      } catch (SlobNotFoundException e) {
        throw new RuntimeException("UDW disappeared right after getOrCreateUdw(): " + udwId);
      }
      ConnectResult udwResult = udwPair.getFirst();
      WaveletName udwWaveletName = IdHack.udwWaveletNameFromConvObjectIdAndUdwObjectId(
          convObjectId, udwId);
      WaveletDataImpl udw = deserializeWavelet(udwWaveletName, udwPair.getSecond());
      WalkaroundWaveletSnapshot udwSnapshot = serializer.createWaveletMessage(udw);
      LoadedUdw loadedUdw = new LoadedUdw(udwId, udwResult, udwSnapshot);
      if (!enableDiffOnOpen) {
        return waveWithoutDiffs(convObjectId, convResult, convWavelet, loadedUdw);
      }

      StringMap<Long> lastReadVersions = getLastReadVersions(udw, convWavelet);

      // The intermediate revision we'll load our wave in, as a simple optimization
      // that avoids loading most of the history in many use cases. We can try to
      // be smarter about this eventually.
      long intermediateVersion = getMinReadVersion(convWavelet, lastReadVersions);
      if (intermediateVersion <= 0 || intermediateVersion > convVersion) {
        throw new AssertionError("Invalid intermediateVersion " + intermediateVersion
          + ", conv version = " + convVersion);
      }

      String intermediateSnapshot;
      if (intermediateVersion == convVersion) {
        intermediateSnapshot = rawConvSnapshot;
      } else {
        try {
          intermediateSnapshot = convStore.loadAtVersion(convObjectId, intermediateVersion);
        } catch (SlobNotFoundException e) {
          throw new RuntimeException(
              "Conv object disappeared when trying to load intermediate version: " + convObjectId);
        }
      }
      WaveletDataImpl intermediateWavelet = deserializeWavelet(convWaveletName,
          intermediateSnapshot);

      Assert.check(intermediateWavelet.getVersion() == intermediateVersion);

      // We have to stop getting the history at conv version, because we're not
      // computing the metadata again for any concurrent ops that got added
      // since we loaded the convResult - so we don't want them getting added
      // into our diff snapshot. We can let the client catch up instead.
      HistoryResult history;
      try {
        history = convStore.loadHistory(convObjectId, intermediateVersion, convVersion);
      } catch (SlobNotFoundException e) {
        throw new RuntimeException(
            "Conv object disappeared when trying to load history: " + convObjectId);
      }

      // Graceful degrade to no diff-on-open if there was too much data.
      // TODO(danilatos): Potentially try to load more history if there's time,
      // or, memcache the current progress so a refresh would have a better
      // chance... or implement composition trees... or some other improvement.
      if (history.hasMore()) {
        return waveWithoutDiffs(convObjectId, convResult, convWavelet, loadedUdw);
      }

      List<String> mutations = mutations(history.getData());

      WaveletDiffSnapshot convSnapshot = serializer.createWaveletDiffMessage(
          intermediateWavelet, convWavelet, lastReadVersions, mutations);
      return new LoadedWave(convObjectId, convResult, convSnapshot, loadedUdw);
    }
  }

  private LoadedWave waveWithoutUdw(SlobId convObjectId,
      ConnectResult convResult, WaveletDataImpl convWavelet) {
    return waveWithoutDiffs(convObjectId, convResult, convWavelet, null);
  }

  private LoadedWave waveWithoutDiffs(SlobId convObjectId,
      ConnectResult convResult, WaveletDataImpl convWavelet, @Nullable LoadedUdw udw) {
    WaveletDiffSnapshot convSnapshot = serializer.createWaveletDiffMessage(convWavelet, convWavelet,
        CollectionUtils.<Long>createStringMap(), Collections.<String>emptyList());
    return new LoadedWave(convObjectId, convResult, convSnapshot, udw);
  }

  /**
   * Creates a mapping of documents to their last read versions in wavelet
   */
  private StringMap<Long> getLastReadVersions(WaveletDataImpl udw, WaveletDataImpl conv) {
    StringMap<Long> lastReadVersions = CollectionUtils.createStringMap();
    WaveletBasedSupplement supplement = WaveletBasedSupplement.create(
        OpBasedWavelet.createReadOnly(udw));

    for (String documentId : conv.getDocumentIds()) {
      long lastReadBlipVersion = supplement.getLastReadBlipVersion(conv.getWaveletId(), documentId);
      if (lastReadBlipVersion == PrimitiveSupplement.NO_VERSION) {
        continue;
      }
      Assert.check(lastReadBlipVersion >= 0);
      lastReadVersions.put(documentId, lastReadBlipVersion);
    }

    return lastReadVersions;
  }

  List<String> mutations(List<ChangeData<String>> data) {
    List<String> mutations = Lists.newArrayList();
    for (ChangeData<String> c : data) {
      mutations.add(c.getPayload());
    }
    return mutations;
  }

  private void log(SlobId objectId, ConnectResult result) {
    log.info("enableUdw: " + enableUdw + ", load(" + objectId + "): " + result);
  }

  /**
   * Calculates the minimum read version of all documents that have been
   * partially read, with a base case of the current wavelet version
   *
   * I.e. if there are no partially read documents, i.e. each document is either
   * entirely read or entirely unread, then the wavelet version is returned.
   *
   * The domain of document ids from the current state of the wavelet is used,
   * rather than the domain of lastReadVersions, in order to filter out read
   * documents that no longer exist.
   */
  private long getMinReadVersion(
      WaveletDataImpl wavelet, StringMap<Long> lastReadVersions) {
    long minVersion = wavelet.getVersion();
    for (String documentId : wavelet.getDocumentIds()) {
      long documentReadVersion = lastReadVersions.get(documentId, 0L);
      long documentModifiedVersion = wavelet.getDocument(documentId).getLastModifiedVersion();

      if (documentReadVersion >= documentModifiedVersion) {
        continue;
      }

      if (documentReadVersion == 0) {
        continue;
      }

      if (documentReadVersion < minVersion) {
        minVersion = documentReadVersion;
      }
    }
    return minVersion;
  }

  private WaveletDataImpl deserializeWavelet(WaveletName waveletName, String snapshot) {
    try {
      return serializer.deserializeWavelet(waveletName, snapshot);
    } catch (MessageException e) {
      throw new RuntimeException("Invalid snapshot for " + waveletName, e);
    }
  }

}
TOP

Related Classes of com.google.walkaround.wave.server.WaveLoader$LoadedWave

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.