/**
* Copyright 2010 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.client;
import com.google.common.base.Preconditions;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.Command;
import org.waveprotocol.wave.client.account.ProfileManager;
import org.waveprotocol.wave.client.account.impl.ProfileManagerImpl;
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.DoodadInstallers;
import org.waveprotocol.wave.client.doodad.diff.DiffAnnotationHandler;
import org.waveprotocol.wave.client.doodad.diff.DiffDeleteRenderer;
import org.waveprotocol.wave.client.doodad.link.LinkAnnotationHandler;
import org.waveprotocol.wave.client.doodad.link.LinkAnnotationHandler.LinkAttributeAugmenter;
import org.waveprotocol.wave.client.doodad.selection.SelectionAnnotationHandler;
import org.waveprotocol.wave.client.doodad.title.TitleAnnotationHandler;
import org.waveprotocol.wave.client.editor.content.Registries;
import org.waveprotocol.wave.client.editor.content.misc.StyleAnnotationHandler;
import org.waveprotocol.wave.client.gadget.Gadget;
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.state.ThreadReadStateMonitor;
import org.waveprotocol.wave.client.state.ThreadReadStateMonitorImpl;
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.impl.diff.DiffController;
import org.waveprotocol.wave.client.wavepanel.impl.reader.Reader;
import org.waveprotocol.wave.client.wavepanel.render.BlipPager;
import org.waveprotocol.wave.client.wavepanel.render.DocumentRegistries;
import org.waveprotocol.wave.client.wavepanel.render.FullDomWaveRendererImpl;
import org.waveprotocol.wave.client.wavepanel.render.InlineAnchorLiveRenderer;
import org.waveprotocol.wave.client.wavepanel.render.LiveConversationViewRenderer;
import org.waveprotocol.wave.client.wavepanel.render.PagingHandlerProxy;
import org.waveprotocol.wave.client.wavepanel.render.ReplyManager;
import org.waveprotocol.wave.client.wavepanel.render.ShallowBlipRenderer;
import org.waveprotocol.wave.client.wavepanel.render.UndercurrentShallowBlipRenderer;
import org.waveprotocol.wave.client.wavepanel.view.ModelIdMapper;
import org.waveprotocol.wave.client.wavepanel.view.ModelIdMapperImpl;
import org.waveprotocol.wave.client.wavepanel.view.ViewIdMapper;
import org.waveprotocol.wave.client.wavepanel.view.dom.DomAsViewProvider;
import org.waveprotocol.wave.client.wavepanel.view.dom.ModelAsViewProvider;
import org.waveprotocol.wave.client.wavepanel.view.dom.ModelAsViewProviderImpl;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.BlipQueueRenderer;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.DomRenderer;
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.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.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 (live) conversations in the wave. */
ObservableConversationView getConversations();
/** @return the core wave. */
ObservableWaveView getWave();
/** @return the signed-in user's (live) supplementary data in the wave. */
LocalSupplementedWave getSupplement();
/** @return live blip read/unread information. */
BlipReadStateMonitor getReadMonitor();
/** @return the registry of document objects used for conversational blips. */
WaveDocuments<? extends InteractiveDocument> getDocumentRegistry();
/** @return the provider of view objects given model objects. */
ModelAsViewProvider getModelAsViewProvider();
/** @return the profile manager. */
ProfileManager getProfileManager();
/** return the id generator. */
IdGenerator getIdGenerator();
/** @return the communication channel connector. */
MuxConnector getConnector();
/**
* @return the blip content renderer, which needs to be flushed by anything
* requiring synchronous rendering.
*/
BlipQueueRenderer getBlipQueue();
/** @return controller of diff state. */
DiffController getDiffController();
/** @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;
// Model objects
private ProfileManager profileManager;
private ObservableConversationView conversations;
private LocalSupplementedWave supplement;
private BlipReadStateMonitor readMonitor;
// State Monitors
private ThreadReadStateMonitor threadReadStateMonitor;
// Rendering objects.
private ViewIdMapper viewIdMapper;
private ShallowBlipRenderer blipDetailer;
private DomRenderer renderer;
private BlipQueueRenderer queueRenderer;
private ModelAsViewProvider modelAsView;
private DiffController diffController;
public DefaultProvider(StageOne stageOne) {
this.stageOne = stageOne;
}
/**
* 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;
}
protected final ViewIdMapper getViewIdMapper() {
return viewIdMapper == null ? viewIdMapper = createViewIdMapper() : viewIdMapper;
}
protected final ShallowBlipRenderer getBlipDetailer() {
return blipDetailer == null ? blipDetailer = createBlipDetailer() : blipDetailer;
}
protected final DomRenderer getRenderer() {
return renderer == null ? renderer = createRenderer() : renderer;
}
protected final ThreadReadStateMonitor getThreadReadStateMonitor() {
return threadReadStateMonitor == null ? threadReadStateMonitor =
createThreadReadStateMonitor() : threadReadStateMonitor;
}
@Override
public final BlipQueueRenderer getBlipQueue() {
return queueRenderer == null ? queueRenderer = createBlipQueueRenderer() : queueRenderer;
}
@Override
public final ModelAsViewProvider getModelAsViewProvider() {
return modelAsView == null ? modelAsView = createModelAsViewProvider() : modelAsView;
}
@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 ProfileManager getProfileManager() {
return profileManager == null ? profileManager = createProfileManager() : profileManager;
}
@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 ObservableConversationView getConversations() {
return conversations == null ? conversations = createConversations() : conversations;
}
@Override
public final LocalSupplementedWave getSupplement() {
return supplement == null ? supplement = createSupplement() : supplement;
}
@Override
public final BlipReadStateMonitor getReadMonitor() {
return readMonitor == null ? readMonitor = createReadMonitor() : readMonitor;
}
@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;
}
@Override
public final DiffController getDiffController() {
return diffController == null ? diffController = createDiffController() : diffController;
}
/** @return the id mangler for model objects. Subclasses may override. */
protected ModelIdMapper createModelIdMapper() {
return ModelIdMapperImpl.create(getConversations(), "UC");
}
/** @return the id mangler for view objects. Subclasses may override. */
protected ViewIdMapper createViewIdMapper() {
return new ViewIdMapper(createModelIdMapper());
}
/** @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, getWaveData().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 user supplement of the wave. Subclasses may override. */
protected LocalSupplementedWave createSupplement() {
Wavelet udw = getWave().getUserData();
if (udw == null) {
udw = getWave().createUserData();
}
ObservablePrimitiveSupplement state = WaveletBasedSupplement.create(udw);
ObservableSupplementedWave live = new LiveSupplementedWaveImpl(
state, getWave(), getSignedInUser(), DefaultFollow.ALWAYS, getConversations());
return LocalSupplementedWaveImpl.create(getWave(), live);
}
/** @return a supplement to the supplement, to get exact read/unread counts. */
protected BlipReadStateMonitor createReadMonitor() {
return BlipReadStateMonitorImpl.create(
getWave().getWaveId(), getSupplement(), getConversations());
}
/** @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();
@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);
}
};
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 = UnsavedDataListenerFactory.NONE;
WaveletId udwId = getIdGenerator().newUserDataWaveletId(getSignedInUser().getAddress());
final IdFilter filter = IdFilter.of(Collections.singleton(udwId),
Collections.singleton(IdConstants.CONVERSATION_WAVELET_PREFIX));
WaveletDataImpl.Factory snapshotFactory =
WaveletDataImpl.Factory.create(getDocumentRegistry());
final OperationChannelMultiplexer mux =
new OperationChannelMultiplexerImpl(getWave().getWaveId(),
viewFactory,
snapshotFactory,
loggers,
unsyncedListeners,
scheduler,
hashFactory);
final WaveViewImpl<OpBasedWavelet> wave = getWave();
return new MuxConnector() {
@Override
public void connect(Command onOpened) {
LiveChannelBinder.openAndBind(getWavelets(),
wave,
getDocumentRegistry(),
mux,
filter,
onOpened);
}
@Override
public void close() {
mux.close();
}
};
}
/** @return the manager of user identities. Subclasses may override. */
protected ProfileManager createProfileManager() {
return new ProfileManagerImpl();
}
/** @return the renderer of intrinsic blip state. Subclasses may override. */
protected ShallowBlipRenderer createBlipDetailer() {
return new UndercurrentShallowBlipRenderer(getProfileManager(), getSupplement());
}
/** @return the thread state monitor. Subclasses may override. */
protected ThreadReadStateMonitor createThreadReadStateMonitor() {
return ThreadReadStateMonitorImpl.create(getSupplement(), getConversations());
}
/** @return the renderer of intrinsic blip state. Subclasses may override. */
protected BlipQueueRenderer createBlipQueueRenderer() {
DomAsViewProvider domAsView = stageOne.getDomAsViewProvider();
ReplyManager replyManager = new ReplyManager(getModelAsViewProvider());
// Add all doodads here.
DocumentRegistries doodads = installDoodads(DocumentRegistries.builder()) // \u2620
.use(InlineAnchorLiveRenderer.installer(getViewIdMapper(), replyManager, domAsView))
.use(Gadget.install(getProfileManager(), getSupplement(), getSignedInUser()))
.build();
LiveConversationViewRenderer live =
LiveConversationViewRenderer.create(SchedulerInstance.getLowPriorityTimer(),
getConversations(), getModelAsViewProvider(), getBlipDetailer(), replyManager,
getThreadReadStateMonitor(), getProfileManager(), getSupplement());
live.init();
BlipPager pager = BlipPager.create(
getDocumentRegistry(), doodads, domAsView, getModelAsViewProvider(), getBlipDetailer(),
stageOne.getWavePanel().getGwtPanel());
// Collect various components required for paging blips in/out.
PagingHandlerProxy pagingHandler = PagingHandlerProxy.create( // \u2620
// Enables and disables the document rendering, as well blip metadata.
pager,
// Registers and deregisters profile listeners for name changes.
live);
return BlipQueueRenderer.create(pagingHandler);
}
protected ViewFactory createViewFactories() {
return ViewFactories.FIXED;
}
protected DomRenderer createRenderer() {
return FullDomWaveRendererImpl.create(getConversations(), getProfileManager(),
getBlipDetailer(), getViewIdMapper(), getBlipQueue(), getThreadReadStateMonitor(),
createViewFactories());
}
protected DiffController createDiffController() {
return DiffController.create(
getConversations(), getSupplement(), getDocumentRegistry(), getModelAsViewProvider());
}
/**
* 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 DocumentRegistries.Builder installDoodads(DocumentRegistries.Builder doodads) {
return doodads.use(new DoodadInstallers.GlobalInstaller() {
@Override
public void install(Registries r) {
DiffAnnotationHandler.register(r.getAnnotationHandlerRegistry(), r.getPaintRegistry());
DiffDeleteRenderer.register(r.getElementHandlerRegistry());
StyleAnnotationHandler.register(r);
TitleAnnotationHandler.register(r);
LinkAnnotationHandler.register(r, new LinkAttributeAugmenter() {
@Override
public Map<String, String> augment(Map<String, Object> annotations, boolean isEditing,
Map<String, String> current) {
return current;
}
});
SelectionAnnotationHandler.register(r, getSessionId(), getProfileManager());
}
});
}
protected ModelAsViewProvider createModelAsViewProvider() {
return new ModelAsViewProviderImpl(getViewIdMapper(), stageOne.getDomAsViewProvider());
}
/**
* 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.
getDiffController().install();
// Install rendering capabilities, then render if necessary.
stageOne.getDomAsViewProvider().setRenderer(getRenderer());
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.
Element e = getRenderer().render(getConversations());
stageOne.getWavePanel().init(e);
}
/**
* Installs the eager features of this stage.
*/
protected void installFeatures() {
// Eagerly install some features.
Reader.install(getSupplement(), stageOne.getFocusFrame(), getModelAsViewProvider(),
getDocumentRegistry());
}
}
}