/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.swellrt.client;
import com.google.common.base.Preconditions;
import com.google.gwt.user.client.Command;
import org.swellrt.model.generic.Model;
import org.waveprotocol.wave.client.OptimalGroupingScheduler;
import org.waveprotocol.wave.client.account.ProfileManager;
import org.waveprotocol.wave.client.common.util.AsyncHolder;
import org.waveprotocol.wave.client.common.util.ClientPercentEncoderDecoder;
import org.waveprotocol.wave.client.common.util.CountdownLatch;
import org.waveprotocol.wave.client.concurrencycontrol.LiveChannelBinder;
import org.waveprotocol.wave.client.concurrencycontrol.MuxConnector;
import org.waveprotocol.wave.client.concurrencycontrol.WaveletOperationalizer;
import org.waveprotocol.wave.client.doodad.link.LinkAnnotationHandler.LinkAttributeAugmenter;
import org.waveprotocol.wave.client.editor.DocOperationLog;
import org.waveprotocol.wave.client.editor.content.Registries;
import org.waveprotocol.wave.client.scheduler.Scheduler.Task;
import org.waveprotocol.wave.client.scheduler.SchedulerInstance;
import org.waveprotocol.wave.client.state.BlipReadStateMonitor;
import org.waveprotocol.wave.client.state.BlipReadStateMonitorImpl;
import org.waveprotocol.wave.client.util.ClientFlags;
import org.waveprotocol.wave.client.wave.InteractiveDocument;
import org.waveprotocol.wave.client.wave.LazyContentDocument;
import org.waveprotocol.wave.client.wave.LocalSupplementedWave;
import org.waveprotocol.wave.client.wave.LocalSupplementedWaveImpl;
import org.waveprotocol.wave.client.wave.RegistriesHolder;
import org.waveprotocol.wave.client.wave.SimpleDiffDoc;
import org.waveprotocol.wave.client.wave.WaveDocuments;
import org.waveprotocol.wave.client.wavepanel.view.ModelIdMapper;
import org.waveprotocol.wave.client.wavepanel.view.ModelIdMapperImpl;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.ViewFactories;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.ViewFactory;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.WavePanelResourceLoader;
import org.waveprotocol.wave.common.logging.LoggerBundle;
import org.waveprotocol.wave.concurrencycontrol.channel.OperationChannelMultiplexer;
import org.waveprotocol.wave.concurrencycontrol.channel.OperationChannelMultiplexerImpl;
import org.waveprotocol.wave.concurrencycontrol.channel.OperationChannelMultiplexerImpl.LoggerContext;
import org.waveprotocol.wave.concurrencycontrol.channel.ViewChannelFactory;
import org.waveprotocol.wave.concurrencycontrol.channel.ViewChannelImpl;
import org.waveprotocol.wave.concurrencycontrol.channel.WaveViewService;
import org.waveprotocol.wave.concurrencycontrol.common.UnsavedDataListener;
import org.waveprotocol.wave.concurrencycontrol.common.UnsavedDataListenerFactory;
import org.waveprotocol.wave.model.conversation.ObservableConversationView;
import org.waveprotocol.wave.model.conversation.WaveBasedConversationView;
import org.waveprotocol.wave.model.document.indexed.IndexedDocumentImpl;
import org.waveprotocol.wave.model.document.operation.DocInitialization;
import org.waveprotocol.wave.model.id.IdConstants;
import org.waveprotocol.wave.model.id.IdFilter;
import org.waveprotocol.wave.model.id.IdGenerator;
import org.waveprotocol.wave.model.id.IdGeneratorImpl;
import org.waveprotocol.wave.model.id.IdGeneratorImpl.Seed;
import org.waveprotocol.wave.model.id.IdURIEncoderDecoder;
import org.waveprotocol.wave.model.id.WaveId;
import org.waveprotocol.wave.model.id.WaveletId;
import org.waveprotocol.wave.model.schema.SchemaProvider;
import org.waveprotocol.wave.model.supplement.LiveSupplementedWaveImpl;
import org.waveprotocol.wave.model.supplement.ObservablePrimitiveSupplement;
import org.waveprotocol.wave.model.supplement.ObservableSupplementedWave;
import org.waveprotocol.wave.model.supplement.SupplementedWaveImpl.DefaultFollow;
import org.waveprotocol.wave.model.supplement.WaveletBasedSupplement;
import org.waveprotocol.wave.model.util.FuzzingBackOffScheduler;
import org.waveprotocol.wave.model.util.FuzzingBackOffScheduler.CollectiveScheduler;
import org.waveprotocol.wave.model.util.Scheduler;
import org.waveprotocol.wave.model.version.HashedVersion;
import org.waveprotocol.wave.model.version.HashedVersionFactory;
import org.waveprotocol.wave.model.version.HashedVersionZeroFactoryImpl;
import org.waveprotocol.wave.model.wave.ParticipantId;
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.data.WaveViewData;
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.ObservableWaveView;
import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet;
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.ArrayList;
import java.util.Collections;
import java.util.Map;
/**
* The second stage of client code.
* <p>
* This stage builds the wave model in memory, and also opens the channel to
* make it live. Rendering code that operates on the model is also established
* in this stage.
*
*/
public interface StageTwo {
/** @return the core wave. */
ObservableWaveView getWave();
/** @return the registry of document objects used for conversational blips. */
WaveDocuments<? extends InteractiveDocument> getDocumentRegistry();
/** @return the id generator. */
IdGenerator getIdGenerator();
/** @return the communication channel connector. */
MuxConnector getConnector();
/** @return the signed in user. */
ParticipantId getSignedInUser();
/** @return a unique string identifying this session. */
String getSessionId();
/** @return stage one. */
StageOne getStageOne();
/**
* Default implementation of the stage two configuration. Each component is
* defined by a factory method, any of which may be overridden in order to
* stub out some dependencies. Circular dependencies are not detected.
*
*/
public static abstract class DefaultProvider extends AsyncHolder.Impl<StageTwo>
implements StageTwo {
// Asynchronously constructed and external dependencies
protected final StageOne stageOne;
private WaveViewData waveData;
//
// Synchronously constructed dependencies.
//
// Client stuff.
private String sessionId;
private ParticipantId signedInuser;
private CollectiveScheduler rpcScheduler;
// Wave stack.
private IdGenerator idGenerator;
private WaveDocuments<LazyContentDocument> documentRegistry;
private WaveletOperationalizer wavelets;
private WaveViewImpl<OpBasedWavelet> wave;
private MuxConnector connector;
private DocOperationLog operationLog; // tracks ops and contributors
// Model objects
private ProfileManager profileManager;
private ObservableConversationView conversations;
private LocalSupplementedWave supplement;
private final UnsavedDataListener unsavedDataListener;
public DefaultProvider(StageOne stageOne, UnsavedDataListener unsavedDataListener) {
this.stageOne = stageOne;
this.unsavedDataListener = unsavedDataListener;
}
/**
* Creates the second stage.
*/
@Override
protected void create(final Accessor<StageTwo> whenReady) {
onStageInit();
final CountdownLatch synchronizer = CountdownLatch.create(2, new Command() {
@Override
public void execute() {
install();
onStageLoaded();
whenReady.use(DefaultProvider.this);
}
});
fetchWave(new Accessor<WaveViewData>() {
@Override
public void use(WaveViewData x) {
waveData = x;
synchronizer.tick();
}
});
// Defer everything else, to let the RPC go out.
SchedulerInstance.getMediumPriorityTimer().scheduleDelayed(new Task() {
@Override
public void execute() {
installStatics();
synchronizer.tick();
}
}, 20);
}
/** Notifies this provider that the stage is about to be loaded. */
protected void onStageInit() {
}
/** Notifies this provider that the stage has been loaded. */
protected void onStageLoaded() {
}
@Override
public final StageOne getStageOne() {
return stageOne;
}
@Override
public final String getSessionId() {
return sessionId == null ? sessionId = createSessionId() : sessionId;
}
@Override
public final ParticipantId getSignedInUser() {
return signedInuser == null ? signedInuser = createSignedInUser() : signedInuser;
}
@Override
public final IdGenerator getIdGenerator() {
return idGenerator == null ? idGenerator = createIdGenerator() : idGenerator;
}
/** @return the scheduler to use for RPCs. */
protected final CollectiveScheduler getRpcScheduler() {
return rpcScheduler == null ? rpcScheduler = createRpcScheduler() : rpcScheduler;
}
@Override
public final MuxConnector getConnector() {
return connector == null ? connector = createConnector() : connector;
}
@Override
public final WaveViewImpl<OpBasedWavelet> getWave() {
return wave == null ? wave = createWave() : wave;
}
protected final WaveletOperationalizer getWavelets() {
return wavelets == null ? wavelets = createWavelets() : wavelets;
}
@Override
public final WaveDocuments<LazyContentDocument> getDocumentRegistry() {
return documentRegistry == null
? documentRegistry = createDocumentRegistry() : documentRegistry;
}
protected final WaveViewData getWaveData() {
Preconditions.checkState(waveData != null, "wave not ready");
return waveData;
}
/** @return the id of the signed-in user. Subclassses may override. */
protected abstract ParticipantId createSignedInUser();
/** @return the unique id for this client session. */
protected abstract String createSessionId();
/** @return the id generator for model object. Subclasses may override. */
protected IdGenerator createIdGenerator() {
final String seed = getSessionId();
// Replace with session.
return new IdGeneratorImpl(getSignedInUser().getDomain(), new Seed() {
@Override
public String get() {
return seed;
}
});
}
/** @return the scheduler to use for RPCs. Subclasses may override. */
protected CollectiveScheduler createRpcScheduler() {
// Use a scheduler that runs closely-timed tasks at the same time.
return new OptimalGroupingScheduler(SchedulerInstance.getLowPriorityTimer());
}
protected WaveletOperationalizer createWavelets() {
return WaveletOperationalizer.create(getWaveData().getWaveId(), getSignedInUser());
}
protected WaveViewImpl<OpBasedWavelet> createWave() {
WaveViewData snapshot = getWaveData();
// The operationalizer makes the wavelets function via operation control.
// The hookup with concurrency-control and remote operation streams occurs
// later in createUpgrader().
final WaveletOperationalizer operationalizer = getWavelets();
WaveletFactory<OpBasedWavelet> waveletFactory = new WaveletFactory<OpBasedWavelet>() {
@Override
public OpBasedWavelet create(WaveId waveId, WaveletId id, ParticipantId creator) {
long now = System.currentTimeMillis();
ObservableWaveletData data = new WaveletDataImpl(id,
creator,
now,
0L,
HashedVersion.unsigned(0),
now,
waveId,
getDocumentRegistry());
return operationalizer.operationalize(data);
}
};
WaveViewImpl<OpBasedWavelet> wave =
WaveViewImpl.create(waveletFactory, snapshot.getWaveId(), getIdGenerator(),
getSignedInUser(), WaveletConfigurator.ADD_CREATOR);
// Populate the initial state.
for (ObservableWaveletData waveletData : snapshot.getWavelets()) {
wave.addWavelet(operationalizer.operationalize(waveletData));
}
return wave;
}
/** @return the conversations in the wave. Subclasses may override. */
protected ObservableConversationView createConversations() {
return WaveBasedConversationView.create(getWave(), getIdGenerator());
}
/** @return the registry of documents in the wave. Subclasses may override. */
protected WaveDocuments<LazyContentDocument> createDocumentRegistry() {
IndexedDocumentImpl.performValidation = false;
DocumentFactory<?> dataDocFactory =
ObservablePluggableMutableDocument.createFactory(createSchemas());
DocumentFactory<LazyContentDocument> blipDocFactory =
new DocumentFactory<LazyContentDocument>() {
private final Registries registries = RegistriesHolder.get();
private final DocOperationLog opLog = DefaultProvider.this.getDocOperationLog();
@Override
public LazyContentDocument create(
WaveletId waveletId, String docId, DocInitialization content) {
// TODO(piotrkaleta,hearnden): hook up real diff state.
SimpleDiffDoc noDiff = SimpleDiffDoc.create(content, null);
return LazyContentDocument.create(registries, noDiff, opLog);
}
};
return WaveDocuments.create(blipDocFactory, dataDocFactory);
}
protected abstract SchemaProvider createSchemas();
/** @return the RPC interface for wave communication. */
protected abstract WaveViewService createWaveViewService();
/** @return upgrader for activating stacklets. Subclasses may override. */
protected MuxConnector createConnector() {
LoggerBundle logger = LoggerBundle.NOP_IMPL;
LoggerContext loggers = new LoggerContext(logger, logger, logger, logger);
IdURIEncoderDecoder uriCodec = new IdURIEncoderDecoder(new ClientPercentEncoderDecoder());
HashedVersionFactory hashFactory = new HashedVersionZeroFactoryImpl(uriCodec);
Scheduler scheduler = new FuzzingBackOffScheduler.Builder(getRpcScheduler())
.setInitialBackOffMs(ClientFlags.get().initialRpcBackoffMs())
.setMaxBackOffMs(ClientFlags.get().maxRpcBackoffMs())
.setRandomisationFactor(0.5)
.build();
ViewChannelFactory viewFactory = ViewChannelImpl.factory(createWaveViewService(), logger);
UnsavedDataListenerFactory unsyncedListeners = new UnsavedDataListenerFactory() {
private final UnsavedDataListener listener = unsavedDataListener;
@Override
public UnsavedDataListener create(WaveletId waveletId) {
return listener;
}
@Override
public void destroy(WaveletId waveletId) {
}
};
WaveletId udwId = getIdGenerator().newUserDataWaveletId(getSignedInUser().getAddress());
ArrayList<String> prefixes = new ArrayList<String>();
prefixes.add(IdConstants.CONVERSATION_WAVELET_PREFIX);
prefixes.add(Model.WAVELET_SWELL_PREFIX);
final IdFilter filter = IdFilter.of(Collections.singleton(udwId), prefixes);
WaveletDataImpl.Factory snapshotFactory =
WaveletDataImpl.Factory.create(getDocumentRegistry());
final OperationChannelMultiplexer mux =
new OperationChannelMultiplexerImpl(getWave().getWaveId(),
viewFactory,
snapshotFactory,
loggers,
unsyncedListeners,
scheduler,
hashFactory, null);
final WaveViewImpl<OpBasedWavelet> wave = getWave();
return new MuxConnector() {
@Override
public void connect(Command onOpened) {
LiveChannelBinder.openAndBind(getWavelets(),
wave,
getDocumentRegistry(),
mux,
filter,
onOpened, getDocOperationLog());
}
@Override
public void close() {
mux.close();
}
};
}
protected ViewFactory createViewFactories() {
return ViewFactories.FIXED;
}
/**
* Fetches and builds the core wave state.
*
* @param whenReady command to execute when the wave is built
*/
protected abstract void fetchWave(final Accessor<WaveViewData> whenReady);
/**
* Installs parts of stage two that have no dependencies.
* <p>
* Subclasses may override this to change the set of installed features.
*/
protected void installStatics() {
WavePanelResourceLoader.loadCss();
}
protected LinkAttributeAugmenter createLinkAttributeAugmenter() {
return new LinkAttributeAugmenter() {
@Override
public Map<String, String> augment(Map<String, Object> annotations, boolean isEditing,
Map<String, String> current) {
return current;
}
};
}
/**
* Installs parts of stage two that have dependencies.
* <p>
* This method is only called once all asynchronously loaded components of
* stage two are ready.
* <p>
* Subclasses may override this to change the set of installed features.
*/
protected void install() {
// Install diff control before rendering, because logical diff state may
// need to be adjusted due to arbitrary UI policies.
ensureRendered();
// Install eager UI features
installFeatures();
// Activate liveness.
getConnector().connect(null);
}
/**
* Ensures that the wave is rendered.
* <p>
* Subclasses may override (e.g., to use server-side rendering).
*/
protected void ensureRendered() {
// Default behaviour is to render the whole wave.
}
/**
* Installs the eager features of this stage.
*/
protected void installFeatures() {
}
/**
* Get the shared wavelet operation logger
*
* @return
*/
protected DocOperationLog getDocOperationLog() {
return operationLog == null ? operationLog = createOperationLog() : operationLog;
}
/**
* Create the shared wavelet operation logger
*
* @return
*/
protected DocOperationLog createOperationLog() {
return new DocOperationLog();
}
}
}