Java tutorial
/* * 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.client; import com.google.common.base.Preconditions; import com.google.common.collect.Iterables; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.GWT.UncaughtExceptionHandler; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.UIObject; import com.google.walkaround.proto.ConnectResponse; import com.google.walkaround.proto.WalkaroundWaveletSnapshot; import com.google.walkaround.proto.WaveletDiffSnapshot; import com.google.walkaround.proto.ClientVars.LiveClientVars; import com.google.walkaround.proto.ClientVars.StaticClientVars; import com.google.walkaround.proto.ClientVars.UdwLoadData; import com.google.walkaround.proto.jso.ClientVarsJsoImpl; import com.google.walkaround.proto.jso.DeltaJsoImpl; import com.google.walkaround.slob.client.GenericOperationChannel.ReceiveOpChannel; import com.google.walkaround.slob.client.GenericOperationChannel.SendOpService; import com.google.walkaround.slob.shared.ChangeData; import com.google.walkaround.slob.shared.MessageException; import com.google.walkaround.slob.shared.SlobId; import com.google.walkaround.util.client.log.ErrorReportingLogHandler; import com.google.walkaround.util.client.log.LogPanel; import com.google.walkaround.util.client.log.Logs; import com.google.walkaround.util.client.log.Logs.Level; import com.google.walkaround.util.client.log.Logs.Log; import com.google.walkaround.util.shared.Assert; import com.google.walkaround.util.shared.RandomBase64Generator; import com.google.walkaround.util.shared.RandomBase64Generator.RandomProvider; import com.google.walkaround.wave.client.MyFullDomRenderer.DocRefRenderer; import com.google.walkaround.wave.client.attachment.DownloadThumbnailAction; import com.google.walkaround.wave.client.attachment.UploadToolbarAction; import com.google.walkaround.wave.client.attachment.WalkaroundAttachmentManager; import com.google.walkaround.wave.client.profile.ContactsManager; import com.google.walkaround.wave.client.rpc.AjaxRpc; import com.google.walkaround.wave.client.rpc.AttachmentInfoService; import com.google.walkaround.wave.client.rpc.ChannelConnectService; import com.google.walkaround.wave.client.rpc.LoadWaveService; import com.google.walkaround.wave.client.rpc.SubmitDeltaService; import com.google.walkaround.wave.client.rpc.WaveletMap; import com.google.walkaround.wave.client.rpc.LoadWaveService.ConnectCallback; import com.google.walkaround.wave.client.rpc.WaveletMap.WaveletEntry; import com.google.walkaround.wave.shared.IdHack; import com.google.walkaround.wave.shared.Versions; import com.google.walkaround.wave.shared.WaveSerializer; import org.waveprotocol.wave.client.StageOne; import org.waveprotocol.wave.client.StageThree; import org.waveprotocol.wave.client.StageTwo; import org.waveprotocol.wave.client.StageZero; import org.waveprotocol.wave.client.Stages; import org.waveprotocol.wave.client.account.Profile; import org.waveprotocol.wave.client.account.ProfileManager; import org.waveprotocol.wave.client.common.util.AsyncHolder; import org.waveprotocol.wave.client.common.util.JsoView; import org.waveprotocol.wave.client.concurrencycontrol.MuxConnector; import org.waveprotocol.wave.client.concurrencycontrol.StaticChannelBinder; import org.waveprotocol.wave.client.concurrencycontrol.WaveletOperationalizer; import org.waveprotocol.wave.client.doodad.DoodadInstallers.ConversationInstaller; import org.waveprotocol.wave.client.doodad.attachment.ImageThumbnail; import org.waveprotocol.wave.client.editor.content.Registries; import org.waveprotocol.wave.client.render.ReductionBasedRenderer; import org.waveprotocol.wave.client.render.RenderingRules; import org.waveprotocol.wave.client.scheduler.SchedulerInstance; import org.waveprotocol.wave.client.scheduler.Scheduler.Task; import org.waveprotocol.wave.client.uibuilder.UiBuilder; import org.waveprotocol.wave.client.wave.LazyContentDocument; 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.render.HtmlDomRenderer; import org.waveprotocol.wave.client.wavepanel.render.DocumentRegistries.Builder; import org.waveprotocol.wave.client.wavepanel.view.BlipView; import org.waveprotocol.wave.client.wavepanel.view.ParticipantView; import org.waveprotocol.wave.client.wavepanel.view.dom.FullStructure; import org.waveprotocol.wave.client.wavepanel.view.dom.ModelAsViewProvider; import org.waveprotocol.wave.client.wavepanel.view.dom.ParticipantAvatarDomImpl; import org.waveprotocol.wave.client.wavepanel.view.dom.UpgradeableDomAsViewProvider; 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.ParticipantAvatarViewBuilder; import org.waveprotocol.wave.client.wavepanel.view.impl.ParticipantViewImpl; import org.waveprotocol.wave.client.widget.popup.AlignedPopupPositioner; import org.waveprotocol.wave.client.widget.profile.ProfilePopupView; import org.waveprotocol.wave.client.widget.profile.ProfilePopupWidget; import org.waveprotocol.wave.concurrencycontrol.channel.WaveViewService; import org.waveprotocol.wave.model.conversation.Conversation; import org.waveprotocol.wave.model.conversation.ConversationBlip; import org.waveprotocol.wave.model.conversation.ConversationThread; import org.waveprotocol.wave.model.document.indexed.IndexedDocumentImpl; import org.waveprotocol.wave.model.document.operation.DocInitialization; import org.waveprotocol.wave.model.document.operation.DocOp; import org.waveprotocol.wave.model.id.IdGenerator; import org.waveprotocol.wave.model.id.ModernIdSerialiser; import org.waveprotocol.wave.model.id.WaveletId; import org.waveprotocol.wave.model.operation.wave.WaveletOperation; import org.waveprotocol.wave.model.schema.SchemaProvider; import org.waveprotocol.wave.model.schema.conversation.ConversationSchemas; import org.waveprotocol.wave.model.testing.RandomProviderImpl; import org.waveprotocol.wave.model.util.CollectionUtils; import org.waveprotocol.wave.model.util.IdentityMap; import org.waveprotocol.wave.model.util.StringMap; import org.waveprotocol.wave.model.wave.InvalidParticipantAddress; 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.WaveViewDataImpl; import org.waveprotocol.wave.model.wave.data.impl.WaveletDataImpl; import javax.annotation.Nullable; /** * Walkaround configuration for Undercurrent client * * @author danilatos@google.com (Daniel Danilatos) */ public class Walkaround implements EntryPoint { // TODO(danilatos): Enable this once Undercurrent does not crash. Or, delete if undercurrent // ends up doing it by default: // // static { // CollectionUtils.setDefaultCollectionFactory(new JsoCollectionFactory()); // } static final Log logger = Logs.create("client"); private static final String WAVEPANEL_PLACEHOLDER = "initialHtml"; // TODO(danilatos): flag private static final int VERSION_CHECK_INTERVAL_MS = 15 * 1000; private static final String OOPHM_SUFFIX = "&gwt.codesvr=127.0.0.1:9997"; private final ClientVarsJsoImpl clientVars = nativeGetVars().cast(); private final WaveSerializer serializer = new WaveSerializer(new ClientMessageSerializer()); private final WaveletMap wavelets = new WaveletMap(); private SlobId convObjectId; private SlobId udwObjectId; private IdGenerator idGenerator; private static native JsoView nativeGetVars() /*-{ return $wnd.__vars; }-*/; private static LogPanel domLogger; private static boolean loaded; private WaveletEntry makeEntry(SlobId objectId, @Nullable ConnectResponse connectResponse, WaveletDataImpl wavelet) { if (connectResponse != null) { Preconditions.checkArgument( objectId.getId().equals(connectResponse.getSignedSession().getSession().getObjectId()), "Mismatched object ids: %s, %s", objectId, connectResponse); } return new WaveletEntry(objectId, connectResponse == null ? null : connectResponse.getSignedSessionString(), connectResponse == null ? null : connectResponse.getSignedSession().getSession(), connectResponse == null ? null : connectResponse.getChannelToken(), wavelet); } private WaveletEntry parseConvWaveletData(@Nullable ConnectResponse connectResponse, WaveletDiffSnapshot diffSnapshot, DocumentFactory<?> docFactory, StringMap<DocOp> diffMap) { WaveSerializer waveSerializer = new WaveSerializer(new ClientMessageSerializer(), docFactory); WaveletDataImpl wavelet; try { StringMap<DocOp> diffOps = waveSerializer.deserializeDocumentsDiffs(diffSnapshot); diffMap.putAll(diffOps); wavelet = waveSerializer.createWaveletData(IdHack.convWaveletNameFromConvObjectId(convObjectId), diffSnapshot); } catch (MessageException e) { throw new RuntimeException(e); } return makeEntry(convObjectId, connectResponse, wavelet); } private WaveletEntry parseUdwData(@Nullable ConnectResponse connectResponse, WalkaroundWaveletSnapshot snapshot, DocumentFactory<?> docFactory) { WaveSerializer serializer = new WaveSerializer(new ClientMessageSerializer(), docFactory); WaveletDataImpl wavelet; try { wavelet = serializer.createWaveletData( IdHack.udwWaveletNameFromConvObjectIdAndUdwObjectId(convObjectId, udwObjectId), snapshot); } catch (MessageException e) { throw new RuntimeException(e); } return makeEntry(udwObjectId, connectResponse, wavelet); } /** * Runs the harness script. */ @Override public void onModuleLoad() { if (loaded) { return; } loaded = true; setupLogging(); setupShell(); // Bail early if the server sent us an error message if (clientVars.hasErrorVars()) { Element placeHolder = Document.get().getElementById(WAVEPANEL_PLACEHOLDER); String errorMessage = "Error: " + clientVars.getErrorVars().getErrorMessage(); if (placeHolder != null) { placeHolder.setInnerText(errorMessage); } else { Window.alert(errorMessage); } return; } logger.log(Level.INFO, "Init"); final SavedStateIndicator indicator = new SavedStateIndicator( Document.get().getElementById("savedStateContainer")); final AjaxRpc rpc = new AjaxRpc("", indicator); final boolean isLive; final boolean useUdw; @Nullable final UdwLoadData udwData; final int randomSeed; final String userIdString; final boolean haveOAuthToken; final String convObjectIdString; final WaveletDiffSnapshot convSnapshot; @Nullable final ConnectResponse convConnectResponse; if (clientVars.hasStaticClientVars()) { isLive = false; StaticClientVars vars = clientVars.getStaticClientVars(); randomSeed = vars.getRandomSeed(); userIdString = vars.getUserEmail(); haveOAuthToken = vars.getHaveOauthToken(); convObjectIdString = vars.getConvObjectId(); convSnapshot = vars.getConvSnapshot(); convConnectResponse = null; useUdw = false; udwData = null; } else { isLive = true; LiveClientVars vars = clientVars.getLiveClientVars(); randomSeed = vars.getRandomSeed(); userIdString = vars.getUserEmail(); haveOAuthToken = vars.getHaveOauthToken(); convObjectIdString = vars.getConvConnectResponse().getSignedSession().getSession().getObjectId(); convSnapshot = vars.getConvSnapshot(); convConnectResponse = vars.getConvConnectResponse(); if (!vars.hasUdw()) { useUdw = false; udwData = null; } else { useUdw = true; udwData = vars.getUdw(); udwObjectId = new SlobId( vars.getUdw().getConnectResponse().getSignedSession().getSession().getObjectId()); } VersionChecker versionChecker = new VersionChecker(rpc, vars.getClientVersion()); // NOTE(danilatos): Use the highest priority timer, since we can't afford to // let it be starved due to some bug with another non-terminating // high-priority task. This task runs infrequently and is very minimal so // the risk of impacting the UI is low. SchedulerInstance.getHighPriorityTimer().scheduleRepeating(versionChecker, VERSION_CHECK_INTERVAL_MS, VERSION_CHECK_INTERVAL_MS); } final RandomProviderImpl random = // TODO(ohler): Get a stronger RandomProvider. RandomProviderImpl.ofSeed(randomSeed); final RandomBase64Generator random64 = new RandomBase64Generator(new RandomProvider() { @Override public int nextInt(int upperBound) { return random.nextInt(upperBound); } }); final ParticipantId userId; try { userId = ParticipantId.of(userIdString); } catch (InvalidParticipantAddress e1) { Window.alert("Invalid user id received from server: " + userIdString); return; } convObjectId = new SlobId(convObjectIdString); idGenerator = new IdHack.MinimalIdGenerator(IdHack.convWaveletIdFromObjectId(convObjectId), useUdw ? IdHack.udwWaveletIdFromObjectId(udwObjectId) // Some code depends on this not to return null, so let's return // something. : IdHack.DISABLED_UDW_ID, random64); // TODO(ohler): Make the server's response to the contacts RPC indicate // whether an OAuth token is needed, and enable the button dynamically when // appropriate, rather than statically. UIObject.setVisible(Document.get().getElementById("enableAvatarsButton"), !haveOAuthToken); @Nullable final LoadWaveService loadWaveService = isLive ? new LoadWaveService(rpc) : null; @Nullable final ChannelConnectService channelService = isLive ? new ChannelConnectService(rpc) : null; new Stages() { @Override protected AsyncHolder<StageZero> createStageZeroLoader() { return new StageZero.DefaultProvider() { @Override protected UncaughtExceptionHandler createUncaughtExceptionHandler() { return WalkaroundUncaughtExceptionHandler.INSTANCE; } }; } @Override protected AsyncHolder<StageOne> createStageOneLoader(StageZero zero) { return new StageOne.DefaultProvider(zero) { protected final ParticipantViewImpl.Helper<ParticipantAvatarDomImpl> participantHelper = new ParticipantViewImpl.Helper<ParticipantAvatarDomImpl>() { @Override public void remove(ParticipantAvatarDomImpl impl) { impl.remove(); } @Override public ProfilePopupView showParticipation(ParticipantAvatarDomImpl impl) { return new ProfilePopupWidget(impl.getElement(), AlignedPopupPositioner.BELOW_RIGHT); } }; @Override protected UpgradeableDomAsViewProvider createViewProvider() { return new FullStructure(createCssProvider()) { @Override public ParticipantView asParticipant(Element e) { return e == null ? null : new ParticipantViewImpl<ParticipantAvatarDomImpl>(participantHelper, ParticipantAvatarDomImpl.of(e)); } }; } }; } @Override protected AsyncHolder<StageTwo> createStageTwoLoader(final StageOne one) { return new StageTwo.DefaultProvider(one, null) { WaveViewData waveData; StringMap<DocOp> diffMap = CollectionUtils.createStringMap(); @Override protected DomRenderer createRenderer() { final BlipQueueRenderer pager = getBlipQueue(); DocRefRenderer docRenderer = new DocRefRenderer() { @Override public UiBuilder render(ConversationBlip blip, IdentityMap<ConversationThread, UiBuilder> replies) { // Documents are rendered blank, and filled in later when // they get paged in. pager.add(blip); return DocRefRenderer.EMPTY.render(blip, replies); } }; RenderingRules<UiBuilder> rules = new MyFullDomRenderer(getBlipDetailer(), docRenderer, getProfileManager(), getViewIdMapper(), createViewFactories(), getThreadReadStateMonitor()) { @Override public UiBuilder render(Conversation conversation, ParticipantId participant) { // Same as super class, but using avatars instead of names. Profile profile = getProfileManager().getProfile(participant); String id = getViewIdMapper().participantOf(conversation, participant); ParticipantAvatarViewBuilder participantUi = ParticipantAvatarViewBuilder .create(id); participantUi.setAvatar(profile.getImageUrl()); participantUi.setName(profile.getFullName()); return participantUi; } }; return new HtmlDomRenderer(ReductionBasedRenderer.of(rules, getConversations())); } @Override protected ProfileManager createProfileManager() { return ContactsManager.create(rpc); } @Override protected void create(final Accessor<StageTwo> whenReady) { super.create(whenReady); } @Override protected IdGenerator createIdGenerator() { return idGenerator; } @Override protected void fetchWave(final Accessor<WaveViewData> whenReady) { wavelets.updateData(parseConvWaveletData(convConnectResponse, convSnapshot, getDocumentRegistry(), diffMap)); if (useUdw) { wavelets.updateData(parseUdwData(udwData.getConnectResponse(), udwData.getSnapshot(), getDocumentRegistry())); } Document.get().getElementById(WAVEPANEL_PLACEHOLDER).setInnerText(""); waveData = createWaveViewData(); whenReady.use(waveData); } @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) { SimpleDiffDoc diff = SimpleDiffDoc.create(content, diffMap.get(docId)); return LazyContentDocument.create(registries, diff); } }; return WaveDocuments.create(blipDocFactory, dataDocFactory); } @Override protected ParticipantId createSignedInUser() { return userId; } @Override protected String createSessionId() { // TODO(ohler): Write a note here about what this is and how it // interacts with walkaround's session management. return random64.next(6); } @Override protected MuxConnector createConnector() { return new MuxConnector() { private void connectWavelet(StaticChannelBinder binder, ObservableWaveletData wavelet) { WaveletId waveletId = wavelet.getWaveletId(); SlobId objectId = IdHack.objectIdFromWaveletId(waveletId); WaveletEntry data = wavelets.get(objectId); Assert.check(data != null, "Unknown wavelet: %s", waveletId); if (data.getChannelToken() == null) { // TODO(danilatos): Handle with a nicer message, and maybe try to // reconnect later. Window.alert("Could not open a live connection to this wave. " + "It will be read-only, changes will not be saved!"); return; } String debugSuffix; if (objectId.equals(convObjectId)) { debugSuffix = "-c"; } else if (objectId.equals(udwObjectId)) { debugSuffix = "-u"; } else { debugSuffix = "-xxx"; } ReceiveOpChannel<WaveletOperation> storeChannel = new GaeReceiveOpChannel<WaveletOperation>( objectId, data.getSignedSessionString(), data.getChannelToken(), channelService, Logs.create("gaeroc" + debugSuffix)) { @Override protected WaveletOperation parse(ChangeData<JavaScriptObject> message) throws MessageException { return serializer .deserializeDelta(message.getPayload().<DeltaJsoImpl>cast()); } }; WalkaroundOperationChannel channel = new WalkaroundOperationChannel( Logs.create("channel" + debugSuffix), createSubmitService(objectId), storeChannel, Versions.truncate(wavelet.getVersion()), data.getSession().getClientId(), indicator); String id = ModernIdSerialiser.INSTANCE.serialiseWaveletId(waveletId); binder.bind(id, channel); } @Override public void connect(Command onOpened) { if (isLive) { WaveletOperationalizer operationalizer = getWavelets(); StaticChannelBinder binder = new StaticChannelBinder(operationalizer, getDocumentRegistry()); for (ObservableWaveletData wavelet : operationalizer.getWavelets()) { if (useUdw || !IdHack.DISABLED_UDW_ID.equals(wavelet.getWaveletId())) { connectWavelet(binder, wavelet); } } // HACK(ohler): I haven't tried to understand what the semantics of the callback // are; perhaps we should invoke it even if the wave is static. (It seems to be // null though.) if (onOpened != null) { onOpened.execute(); } } } @Override public void close() { throw new AssertionError("close not implemented"); } }; } private SubmitDeltaService createSubmitService(final SlobId objectId) { return new SubmitDeltaService(rpc, wavelets, objectId) { @Override public void requestRevision(final SendOpService.Callback callback) { loadWaveService.fetchWaveRevision(wavelets.get(objectId).getSignedSessionString(), new ConnectCallback() { @Override public void onData(ConnectResponse data) { // TODO(danilatos): Update session id etc in the operation channel // in order for (something equivalent to) this to work. // But don't have submit delta service keep a reference to this map. // ALSO TODO: channelToken could be null, if a channel could not be opened. // In this case the object should be opened as readonly. //wavelets.updateData( // new WaveletEntry(id, channelToken, sid, xsrfToken, revision, null)); callback.onSuccess(Versions.truncate(data.getObjectVersion())); } @Override public void onConnectionError(Throwable e) { callback.onConnectionError(e); } }); } }; } @Override protected WaveViewService createWaveViewService() { // unecessary throw new UnsupportedOperationException(); } @Override protected SchemaProvider createSchemas() { return new ConversationSchemas(); } @Override protected Builder installDoodads(Builder doodads) { doodads = super.installDoodads(doodads); doodads.use(new ConversationInstaller() { @Override public void install(Wavelet w, Conversation c, Registries r) { WalkaroundAttachmentManager mgr = new WalkaroundAttachmentManager( new AttachmentInfoService(rpc), logger); ImageThumbnail.register(r.getElementHandlerRegistry(), mgr, new DownloadThumbnailAction()); } }); return doodads; } @Override protected void installFeatures() { super.installFeatures(); ReadStateSynchronizer.create(FakeReadStateService.create(), getReadMonitor()); } }; } @Override protected AsyncHolder<StageThree> createStageThreeLoader(final StageTwo two) { return new StageThree.DefaultProvider(two) { @Override protected void install() { // Inhibit if not live; super.install() seems to enable editing in Undercurrent. // Haven't studied this carefully though. if (isLive) { super.install(); } } @Override protected boolean getAttachmentButtonEnabled() { return false; } @Override protected void create(final Accessor<StageThree> whenReady) { // Prepend an init wave flow onto the stage continuation. super.create(new Accessor<StageThree>() { @Override public void use(StageThree three) { if (isLive) { maybeNewWaveSetup(two, three); UploadToolbarAction.install(three.getEditSession(), three.getEditToolbar()); // HACK(ohler): I haven't tried to understand what the semantics of the callback // are; perhaps we should invoke it even if the wave is static. whenReady.use(three); } } }); } }; } private void maybeNewWaveSetup(StageTwo two, StageThree three) { ModelAsViewProvider views = two.getModelAsViewProvider(); Conversation rootConv = two.getConversations().getRoot(); if (looksLikeANewWave(rootConv)) { BlipView blipUi = views.getBlipView(rootConv.getRootThread().getFirstBlip()); // Needed because startEditing must have an editor already rendered. two.getBlipQueue().flush(); three.getEditActions().startEditing(blipUi); } } private boolean looksLikeANewWave(Conversation rootConv) { return Iterables.size(rootConv.getRootThread().getBlips()) == 1 && rootConv.getRootThread().getFirstBlip().getContent().size() == 4; } }.load(null); } private WaveViewDataImpl createWaveViewData() { final WaveViewDataImpl waveData = WaveViewDataImpl.create(IdHack.waveIdFromConvObjectId(convObjectId)); wavelets.each(new WaveletMap.Proc() { @Override public void wavelet(WaveletEntry data) { WaveletDataImpl wavelet = data.getWaveletState(); waveData.addWavelet(wavelet); } }); return waveData; } private void setupShell() { DebugMenu menu = new DebugMenu(); boolean shouldShowDebug = Window.Location.getParameter("debug") != null; menu.setVisible(shouldShowDebug); menu.addItem("OOPHM", navigateTaskOophm(Window.Location.getHref(), false)); menu.addItem("Show log", new Task() { @Override public void execute() { Walkaround.ensureDomLog(); } }); menu.addItem("log level 'debug'", navigateTask(Window.Location.getHref() + "&ll=debug", false)); menu.addItem("Frame: " + Window.Location.getHref(), navigateTask(Window.Location.getHref(), true)); menu.addItem("Throw Exception", new Task() { @Override public void execute() { throw new RuntimeException("TEST EXCEPTION", a()); } private Exception a() { return b(); } private Exception b() { return new Exception("Nested test exception cause"); } }); menu.install(); } private void setupLogging() { Logs.get().addHandler(new ErrorReportingLogHandler("/gwterr") { @Override protected void onSevere(String stream) { alertDomLog(stream); } }); } /** * If ths DOM log has not been created yet, creates it and draws attention to * a stream. */ private static void alertDomLog(final String attentionStream) { if (domLogger == null) { domLogger = LogPanel.createOnStream(Logs.get(), attentionStream); attachLogPanel(); } } /** * If ths DOM log has not been created yet, creates it. */ private static void ensureDomLog() { if (domLogger == null) { domLogger = LogPanel.create(Logs.get()); attachLogPanel(); } } private static Task navigateTaskOophm(String url, boolean newPage) { if (!url.endsWith(OOPHM_SUFFIX)) { url += OOPHM_SUFFIX; } return navigateTask(url, newPage); } private static Task navigateTask(final String url, final boolean newPage) { return new Task() { @Override public void execute() { if (newPage) { Window.open(url, "_blank", null); } else { Window.Location.assign(url); } } }; } /** Reveals the log div, and executes a task when done. */ // The async API for this method is intended to support two things: a cool // spew animation, and also the potential to runAsync the whole LogPanel code. private static void attachLogPanel() { Logs.get().addHandler(domLogger); final Panel logHolder = RootPanel.get("logHolder"); logHolder.setVisible(true); // Need one layout and paint cycle after revealing it to start animation. // Use high priority to avoid potential starvation by other tasks if a // problem is occurring. SchedulerInstance.getHighPriorityTimer().scheduleDelayed(new Task() { @Override public void execute() { logHolder.add(domLogger); Style waveStyle = Document.get().getElementById(WAVEPANEL_PLACEHOLDER).getStyle(); Style logStyle = logHolder.getElement().getStyle(); logStyle.setHeight(250, Unit.PX); waveStyle.setBottom(250, Unit.PX); } }, 50); } }