Package org.waveprotocol.wave.concurrencycontrol.wave

Source Code of org.waveprotocol.wave.concurrencycontrol.wave.CcBasedWaveViewImpl$TerminalWavelets

/**
* 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.common.logging.LoggerBundle;
import org.waveprotocol.wave.concurrencycontrol.channel.Accessibility;
import org.waveprotocol.wave.concurrencycontrol.channel.OperationChannel;
import org.waveprotocol.wave.concurrencycontrol.channel.OperationChannelMultiplexer;
import org.waveprotocol.wave.concurrencycontrol.common.CorruptionDetail;
import org.waveprotocol.wave.concurrencycontrol.common.ResponseCode;
import org.waveprotocol.wave.concurrencycontrol.wave.CcBasedWavelet.FailureHandler;
import org.waveprotocol.wave.model.id.IdFilter;
import org.waveprotocol.wave.model.id.IdGenerator;
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.wave.WaveletOperationContext;
import org.waveprotocol.wave.model.schema.SchemaProvider;
import org.waveprotocol.wave.model.util.CollectionUtils;
import org.waveprotocol.wave.model.util.IdentitySet;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.model.wave.ParticipationHelper;
import org.waveprotocol.wave.model.wave.WaveViewListener;
import org.waveprotocol.wave.model.wave.Wavelet;
import org.waveprotocol.wave.model.wave.data.DocumentFactory;
import org.waveprotocol.wave.model.wave.data.ObservableWaveletData;
import org.waveprotocol.wave.model.wave.opbased.WaveViewImpl;
import org.waveprotocol.wave.model.wave.opbased.WaveViewImpl.WaveletConfigurator;
import org.waveprotocol.wave.model.wave.opbased.WaveViewImpl.WaveletFactory;

import java.util.Map;

/**
* An implementation of a wave view based on wavelets that sit on top of a
* concurrency-control stack.
*
*/
public final class CcBasedWaveViewImpl implements CcBasedWaveView {

  /**
   * Handler for the view becoming disconnected.
   *
   * @see WaveDisconnectedHandler
   */
  public interface DisconnectedHandler {
    /**
     * Called when this wave becomes permanently disconnected
     */
    void onWaveDisconnected(CorruptionDetail detail);
  }

  /**
   * A factory for flushable documents, that also remembers documents it has
   * created.
   */
  public interface CcDocumentFactory<D extends CcDocument> extends DocumentFactory<D> {
    /**
     * Gets the document that was created for a particular document identifier.
     *
     * @param waveletId wavelet in which the document exists
     * @param blipId blip in which the document exists
     * @return the document that was previously created for blip {@code blipId}
     *         in wavelet {@code waveletId}, or {@code null}.
     */
    D get(WaveletId waveletId, String blipId);
  }

  /**
   * Listens to a mux, placing wavelet models on top of operation channels
   * that show up.  Such wavelets are then passed to a listener.
   */
  private static class MuxListener implements OperationChannelMultiplexer.Listener {
    /**
     * Interface through which this listener notifies something of new wavelets
     * that show up on the mux.
     */
    interface WaveletListener {
      void onWaveletAdded(CcBasedWavelet w);
      void onWaveletRemoved(CcBasedWavelet w);
    }

    /**
     * Interface through which mux listeners are created.
     */
    interface Factory {
      MuxListener create(OpenListener openListener);
    }

    private final CcBasedWavelet.Factory waveletFactory;
    private final DisconnectedHandler disconnectedHandler;
    private final WaveletListener listener;
    private final OpenListener openListener;
    private final Map<WaveletId, CcBasedWavelet> wavelets = CollectionUtils.newHashMap();
    private final TerminalWavelets terminalWavelets;

    MuxListener(CcBasedWavelet.Factory waveletFactory, DisconnectedHandler disconnectedHandler,
        WaveletListener listener, OpenListener openListener,
        TerminalWavelets terminalWavelets) {
      this.waveletFactory = waveletFactory;
      this.disconnectedHandler = disconnectedHandler;
      this.listener = listener;
      this.openListener = openListener;
      this.terminalWavelets = terminalWavelets;
    }

    @Override
    public void onOperationChannelCreated(OperationChannel channel, ObservableWaveletData snapshot,
        Accessibility accessibility) {
      // New wavelet has come into existence. Build a model on top.
      CcBasedWavelet wavelet = waveletFactory.create(channel, snapshot);

      // Keep this wavelet around for future changes.
      wavelets.put(wavelet.getId(), wavelet);

      // Mark inaccessible if necessary.
      if (accessibility == Accessibility.INACCESSIBLE) {
        terminalWavelets.markTerminal(wavelet);
      }

      listener.onWaveletAdded(wavelet);
    }

    @Override
    public void onOperationChannelRemoved(OperationChannel channel, WaveletId waveletId) {
      CcBasedWavelet wavelet = wavelets.remove(waveletId);
      terminalWavelets.clearTerminal(wavelet);
      listener.onWaveletRemoved(wavelet);
    }

    @Override
    public void onFailed(CorruptionDetail detail) {
      if (disconnectedHandler != null) {
        disconnectedHandler.onWaveDisconnected(detail);
      }
    }

    @Override
    public void onOpenFinished() {
      openListener.onOpenFinished();
    }
  }

  /**
   * Tracks terminal wavelets across all wavelet channels.
   * {@see CcBasedWaveView#isTerminal(Wavelet)}.
   */
  private static class TerminalWavelets {
    private final IdentitySet<Wavelet> terminalWavelets = CollectionUtils.createIdentitySet();

    /**
     * Marks a wavelet as terminal.
     */
    public void markTerminal(Wavelet wavelet) {
      terminalWavelets.add(wavelet);
    }

    /**
     * Clears the terminated status of a wavelet.
     */
    public void clearTerminal(Wavelet wavelet) {
      terminalWavelets.remove(wavelet);
    }

    /**
     * @return whether a given wavelet is terminal
     */
    public boolean isTerminal(Wavelet wavelet) {
      return terminalWavelets.contains(wavelet);
    }
  }

  private final WaveViewImpl<CcBasedWavelet> view;
  private final OperationChannelMultiplexer pipe;
  private final IdFilter waveletFilter;
  private final MuxListener.Factory muxListenerFactory;
  private final TerminalWavelets terminalWavelets;

  /**
   * Creates a wave view. The new view needs to be {@link #open(OpenListener)
   * opened} before use.
   *
   * @return a new wave view.
   */
  public static CcBasedWaveView create(CcDocumentFactory<?> docFactory, SchemaProvider schemas,
      WaveId waveId, ParticipantId userId,
      final OperationChannelMultiplexer pipe, IdFilter waveletFilter,
      IdGenerator idGenerator, final LoggerBundle logger,
      WaveletOperationContext.Factory contextFactory, ParticipationHelper participationHelper,
      final DisconnectedHandler disconnectedHandler, WaveletConfigurator configurator,
      DuplexOpSinkFactory tapsFactory) {
    // Local class to overcome the impedance mismatch between:
    // *) the view implementation's policy of notifying listeners as soons as a
    // new wavelet shows up;
    // *) the mux's model of creating a new wavelet by creating a new operation
    // channel (which makes it look like a new wavelet has just showed up); and
    // *) the need to delay notifying listeners of new wavelets until after the
    // configurator has had a chance to prepare it.
    class LocalWaveletHolder {
      private boolean isExpecting;
      private CcBasedWavelet w;

      void expect() {
        isExpecting = true;
      }

      boolean isExpecting() {
        return isExpecting;
      }

      void push(CcBasedWavelet w) {
        this.w = w;
        isExpecting = false;
      }

      CcBasedWavelet pop() {
        CcBasedWavelet toReturn = w;
        w = null;
        return toReturn;
      }
    }

    // Handle wavelet failure by closing the mux (ensuring no more ops are
    // received) and disconnecting the wave (ensuring no more local
    // modifications are made).
    FailureHandler failureHandler = new FailureHandler() {
      @Override
      public void onWaveletFailed(OperationException failure) {
        logger.error().log("CcBasedWavelet failed permanently", failure);
        disconnectedHandler.onWaveDisconnected(
            new CorruptionDetail(failure.isSchemaViolation()
                ? ResponseCode.SCHEMA_VIOLATION : ResponseCode.INVALID_OPERATION,
                "CcBasedWavelet failed",
                failure));
        pipe.close();
      }
    };

    final CcBasedWavelet.Factory waveletFactory =
        new CcBasedWavelet.Factory(waveId, contextFactory, participationHelper, docFactory,
            failureHandler, tapsFactory);

    final LocalWaveletHolder holder = new LocalWaveletHolder();
    WaveletFactory<CcBasedWavelet> factory = new WaveletFactory<CcBasedWavelet>() {
      @Override
      public CcBasedWavelet create(WaveId waveId, WaveletId waveletId, ParticipantId creator) {
        // For wavelets created by this view instance, it is undesirable to
        // notify listeners of the new wavelet here; it is better to notify
        // listeners right before the exit of createWavelet(), AFTER the
        // WaveletConfigurator has initialized the new wavelet (e.g., in case
        // the creating account needs to be added as a participant). This is to
        // prevent callbacks from making mutations to the wavelet before it has
        // been configured.
        holder.expect();
        assert holder.isExpecting();
        pipe.createOperationChannel(waveletId, creator);
        // This should have created a new wavelet through the
        // onOperationChannelCreated callback.
        assert !holder.isExpecting();
        return holder.pop();
      }
    };

    final WaveViewImpl<CcBasedWavelet> view =
        WaveViewImpl.create(factory, waveId, idGenerator, userId, configurator);

    final MuxListener.WaveletListener waveletListener = new MuxListener.WaveletListener() {
      @Override
      public void onWaveletAdded(CcBasedWavelet wavelet) {
        if (view.getWavelet(wavelet.getId()) != null) {
          logger.error().log(
              "Ignoring new channel for existing wavelet " + wavelet.getId() + " in wave "
              + view.getWaveId());
        } else {
          if (holder.isExpecting()) {
            holder.push(wavelet);
          } else {
            view.addWavelet(wavelet);
          }
        }
      }

      @Override
      public void onWaveletRemoved(CcBasedWavelet wavelet) {
        view.removeWavelet(wavelet);
      }
    };

    final TerminalWavelets accessibilityTracker = new TerminalWavelets();

    MuxListener.Factory muxListenerFactory = new MuxListener.Factory() {
      @Override
      public MuxListener create(OpenListener openListener) {
        return new MuxListener(waveletFactory, disconnectedHandler, waveletListener,
            openListener, accessibilityTracker);
      }
    };

    return new CcBasedWaveViewImpl(
        pipe, waveletFilter, view, muxListenerFactory, accessibilityTracker);
  }

  /**
   * Creates a wave view (dependency injection constructor).
   */
  private CcBasedWaveViewImpl(OperationChannelMultiplexer pipe, IdFilter waveletFilter,
      WaveViewImpl<CcBasedWavelet> view, MuxListener.Factory muxListenerFactory,
      TerminalWavelets terminalWavelets) {
    this.pipe = pipe;
    this.waveletFilter = waveletFilter;
    this.view = view;
    this.muxListenerFactory = muxListenerFactory;
    this.terminalWavelets = terminalWavelets;
  }

  //
  // Channel lifecycle.
  //

  public void open(OpenListener openListener) {
    pipe.open(muxListenerFactory.create(openListener), waveletFilter);
  }

  public void close() {
    pipe.close();
  }

  //
  // Accessibility.
  //

  @Override
  public boolean isTerminal(Wavelet wavelet) {
    return terminalWavelets.isTerminal(wavelet);
  }

  //
  // WaveView implementation.
  //

  @Override
  public CcBasedWavelet createRoot() {
    return view.createRoot();
  }

  @Override
  public CcBasedWavelet createWavelet() {
    return view.createWavelet();
  }

  @Override
  public CcBasedWavelet createUserData() {
    return view.createUserData();
  }

  @Override
  public CcBasedWavelet getWavelet(WaveletId waveletId) {
    return view.getWavelet(waveletId);
  }

  @Override
  public CcBasedWavelet getRoot() {
    return view.getRoot();
  }

  @Override
  public CcBasedWavelet getUserData() {
    return view.getUserData();
  }

  @Override
  public Iterable<? extends CcBasedWavelet> getWavelets() {
    return view.getWavelets();
  }

  @Override
  public WaveId getWaveId() {
    return view.getWaveId();
  }

  //
  // Observable extension.
  //

  @Override
  public void addListener(WaveViewListener listener) {
    view.addListener(listener);
  }

  @Override
  public void removeListener(WaveViewListener listener) {
    view.removeListener(listener);
  }
}
TOP

Related Classes of org.waveprotocol.wave.concurrencycontrol.wave.CcBasedWaveViewImpl$TerminalWavelets

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.