Package org.waveprotocol.box.server.frontend

Source Code of org.waveprotocol.box.server.frontend.ClientFrontendImpl

/**
* 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.frontend;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Sets;

import org.waveprotocol.box.common.DeltaSequence;
import org.waveprotocol.box.common.comms.WaveClientRpc;
import org.waveprotocol.box.server.waveserver.WaveBus;
import org.waveprotocol.box.server.waveserver.WaveServerException;
import org.waveprotocol.box.server.waveserver.WaveletProvider;
import org.waveprotocol.box.server.waveserver.WaveletProvider.SubmitRequestListener;
import org.waveprotocol.wave.federation.Proto.ProtocolWaveletDelta;
import org.waveprotocol.wave.model.id.IdFilter;
import org.waveprotocol.wave.model.id.WaveId;
import org.waveprotocol.wave.model.id.WaveletId;
import org.waveprotocol.wave.model.id.WaveletName;
import org.waveprotocol.wave.model.operation.wave.AddParticipant;
import org.waveprotocol.wave.model.operation.wave.RemoveParticipant;
import org.waveprotocol.wave.model.operation.wave.TransformedWaveletDelta;
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.model.wave.data.ReadableWaveletData;
import org.waveprotocol.wave.util.logging.Log;

import java.util.Collection;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;


/**
* Implements {@link ClientFrontend}.
*
* When a wavelet is added and it's not at version 0, buffer updates until a
* request for the wavelet's history has completed.
*/
public class ClientFrontendImpl implements ClientFrontend, WaveBus.Subscriber {
  private static final Log LOG = Log.get(ClientFrontendImpl.class);

  private final static AtomicInteger channel_counter = new AtomicInteger(0);

  private final WaveletProvider waveletProvider;
  private final WaveletInfo waveletInfo;

  /**
   * Creates a client frontend and subscribes it to the wave bus.
   *
   * @throws WaveServerException if the server fails during initialization.
   */
  public static ClientFrontendImpl create(WaveletProvider waveletProvider, WaveBus wavebus,
      WaveletInfo waveletInfo) throws WaveServerException {

    ClientFrontendImpl impl =
        new ClientFrontendImpl(waveletProvider, waveletInfo);

    wavebus.subscribe(impl);
    return impl;
  }

  /**
   * Constructor.
   *
   * @param waveletProvider
   * @param waveDomain the server wave domain. It is assumed that the wave domain is valid.
   */
  @VisibleForTesting
  ClientFrontendImpl(
      WaveletProvider waveletProvider, WaveletInfo waveletInfo) {
    this.waveletProvider = waveletProvider;
    this.waveletInfo = waveletInfo;
  }

  @Override
  public void openRequest(ParticipantId loggedInUser, WaveId waveId, IdFilter waveletIdFilter,
      Collection<WaveClientRpc.WaveletVersion> knownWavelets, OpenListener openListener) {
    LOG.info("received openRequest from " + loggedInUser + " for " + waveId + ", filter "
        + waveletIdFilter + ", known wavelets: " + knownWavelets);

    // TODO(josephg): Make it possible for this to succeed & return public waves.
    if (loggedInUser == null) {
      openListener.onFailure("Not logged in");
      return;
    }

    if (!knownWavelets.isEmpty()) {
      openListener.onFailure("Known wavelets not supported");
      return;
    }

    try {
      waveletInfo.initialiseWave(waveId);
    } catch (WaveServerException e) {
      LOG.severe("Wave server failed lookup for " + waveId, e);
      openListener.onFailure("Wave server failed to look up wave");
      return;
    }

    String channelId = generateChannelID();
    UserManager userManager = waveletInfo.getUserManager(loggedInUser);
    synchronized (userManager) {
      WaveViewSubscription subscription =
          userManager.subscribe(waveId, waveletIdFilter, channelId, openListener);
      LOG.info("Subscribed " + loggedInUser + " to " + waveId + " channel " + channelId);

      Set<WaveletId> waveletIds;
      try {
        waveletIds = waveletInfo.visibleWaveletsFor(subscription, loggedInUser);
      } catch (WaveServerException e1) {
        waveletIds = Sets.newHashSet();
        LOG.warning("Failed to retrieve visible wavelets for " + loggedInUser, e1);
      }
      for (WaveletId waveletId : waveletIds) {
        WaveletName waveletName = WaveletName.of(waveId, waveletId);
        // Ensure that implicit participants will also receive updates.
        // TODO (Yuri Z.) If authorizing participant was removed from the wave
        // (the shared domain participant), then all implicit participant that
        // were authorized should be unsubsrcibed.
        waveletInfo.notifyAddedImplcitParticipant(waveletName, loggedInUser);
        // The WaveletName by which the waveletProvider knows the relevant deltas

        // TODO(anorth): if the client provides known wavelets, calculate
        // where to start sending deltas from.

        CommittedWaveletSnapshot snapshotToSend;

        // Send a snapshot of the current state.
        // TODO(anorth): calculate resync point if the client already knows
        // a snapshot.
        try {
          snapshotToSend = waveletProvider.getSnapshot(waveletName);
        } catch (WaveServerException e) {
          LOG.warning("Failed to retrieve snapshot for wavelet " + waveletName, e);
          openListener.onFailure("Wave server failure retrieving wavelet");
          return;
        }

        LOG.info("snapshot in response is: " + (snapshotToSend != null));
        if (snapshotToSend == null) {
          // Send deltas.
          openListener.onUpdate(waveletName, snapshotToSend, DeltaSequence.empty(),
              null, null, channelId);
        } else {
          // Send the snapshot.
          openListener.onUpdate(waveletName, snapshotToSend, DeltaSequence.empty(),
              snapshotToSend.committedVersion, null, channelId);
        }
      }

      WaveletName dummyWaveletName = createDummyWaveletName(waveId);
      if (waveletIds.size() == 0) {
        // Send message with just the channel id.
        LOG.info("sending just a channel id for " + dummyWaveletName);
        openListener.onUpdate(dummyWaveletName, null, DeltaSequence.empty(), null, null,
            channelId);
      }
      LOG.info("sending marker for " + dummyWaveletName);
      openListener.onUpdate(dummyWaveletName, null, DeltaSequence.empty(), null, true, null);
    }
  }

  private String generateChannelID() {
    return "ch" + channel_counter.addAndGet(1);
  }

  @Override
  public void submitRequest(ParticipantId loggedInUser, final WaveletName waveletName,
      final ProtocolWaveletDelta delta, final String channelId,
      final SubmitRequestListener listener) {
    final ParticipantId author = new ParticipantId(delta.getAuthor());

    if (!author.equals(loggedInUser)) {
      listener.onFailure("Author field on delta must match logged in user");
      return;
    }

    waveletInfo.getUserManager(author).submitRequest(channelId, waveletName);
    waveletProvider.submitRequest(waveletName, delta, new SubmitRequestListener() {
      @Override
      public void onSuccess(int operationsApplied,
          HashedVersion hashedVersionAfterApplication, long applicationTimestamp) {
        listener.onSuccess(operationsApplied, hashedVersionAfterApplication,
            applicationTimestamp);
        waveletInfo.getUserManager(author).submitResponse(channelId, waveletName,
            hashedVersionAfterApplication);
      }

      @Override
      public void onFailure(String error) {
        listener.onFailure(error);
        waveletInfo.getUserManager(author).submitResponse(channelId, waveletName, null);
      }
    });
  }

  @Override
  public void waveletCommitted(WaveletName waveletName, HashedVersion version) {
    for (ParticipantId participant : waveletInfo.getWaveletParticipants(waveletName)) {
      waveletInfo.getUserManager(participant).onCommit(waveletName, version);
    }
  }

  /**
   * Sends new deltas to a particular user on a particular wavelet.
   * Updates the participants of the specified wavelet if the participant was added or removed.
   *
   * @param waveletName the waveletName which the deltas belong to.
   * @param participant on the wavelet.
   * @param newDeltas newly arrived deltas of relevance for participant. Must
   *        not be empty.
   * @param add whether the participant is added by the first delta.
   * @param remove whether the participant is removed by the last delta.
   */
  private void participantUpdate(WaveletName waveletName, ParticipantId participant,
      DeltaSequence newDeltas, boolean add, boolean remove) {
    if (add) {
      waveletInfo.notifyAddedExplicitWaveletParticipant(waveletName, participant);
    }
    waveletInfo.getUserManager(participant).onUpdate(waveletName, newDeltas);
    if (remove) {
      waveletInfo.notifyRemovedExplicitWaveletParticipant(waveletName, participant);
    }
  }

  /**
   * Tracks wavelet versions and ensures that the deltas are contiguous. Updates
   * wavelet subscribers with new new deltas.
   */
  @Override
  public void waveletUpdate(ReadableWaveletData wavelet, DeltaSequence newDeltas) {
    if (newDeltas.isEmpty()) {
      return;
    }

    WaveletName waveletName = WaveletName.of(wavelet.getWaveId(), wavelet.getWaveletId());
    waveletInfo.syncWaveletVersion(waveletName, newDeltas);

    Set<ParticipantId> remainingparticipants =
        Sets.newHashSet(waveletInfo.getWaveletParticipants(waveletName));
    // Participants added during the course of newDeltas.
    Set<ParticipantId> newParticipants = Sets.newHashSet();
    for (int i = 0; i < newDeltas.size(); i++) {
      TransformedWaveletDelta delta = newDeltas.get(i);
      // Participants added or removed in this delta get the whole delta.
      for (WaveletOperation op : delta) {
        if (op instanceof AddParticipant) {
          ParticipantId p = ((AddParticipant) op).getParticipantId();
          remainingparticipants.add(p);
          newParticipants.add(p);
        }
        if (op instanceof RemoveParticipant) {
          ParticipantId p = ((RemoveParticipant) op).getParticipantId();
          remainingparticipants.remove(p);
          participantUpdate(waveletName, p, newDeltas.subList(0, i + 1), newParticipants.remove(p),
              true);
        }
      }
    }

    // Send out deltas to those who end up being participants at the end
    // (either because they already were, or because they were added).
    for (ParticipantId p : remainingparticipants) {
      boolean isNew = newParticipants.contains(p);
      participantUpdate(waveletName, p, newDeltas, isNew, false);
    }
  }

  @VisibleForTesting
  static WaveletName createDummyWaveletName(WaveId waveId) {
    final WaveletName dummyWaveletName =
      WaveletName.of(waveId, WaveletId.of(waveId.getDomain(), "dummy+root"));
    return dummyWaveletName;
  }
}
TOP

Related Classes of org.waveprotocol.box.server.frontend.ClientFrontendImpl

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.