org.rstudio.studio.client.application.Application.java Source code

Java tutorial

Introduction

Here is the source code for org.rstudio.studio.client.application.Application.java

Source

/*
 * Application.java
 *
 * Copyright (C) 2009-12 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

package org.rstudio.studio.client.application;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.RunAsyncCallback;
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.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;

import org.rstudio.core.client.Barrier;
import org.rstudio.core.client.BrowseCap;
import org.rstudio.core.client.Debug;
import org.rstudio.core.client.Barrier.Token;
import org.rstudio.core.client.command.CommandBinder;
import org.rstudio.core.client.command.Handler;
import org.rstudio.core.client.dom.DomUtils;
import org.rstudio.core.client.events.BarrierReleasedEvent;
import org.rstudio.core.client.events.BarrierReleasedHandler;
import org.rstudio.core.client.widget.Operation;
import org.rstudio.studio.client.application.events.*;
import org.rstudio.studio.client.application.model.ProductInfo;
import org.rstudio.studio.client.application.model.SessionSerializationAction;
import org.rstudio.studio.client.application.ui.AboutDialog;
import org.rstudio.studio.client.application.ui.RequestLogVisualization;
import org.rstudio.studio.client.common.GlobalDisplay;
import org.rstudio.studio.client.common.SimpleRequestCallback;
import org.rstudio.studio.client.common.SuperDevMode;
import org.rstudio.studio.client.common.satellite.SatelliteManager;
import org.rstudio.studio.client.projects.Projects;
import org.rstudio.studio.client.server.*;
import org.rstudio.studio.client.workbench.ClientStateUpdater;
import org.rstudio.studio.client.workbench.Workbench;
import org.rstudio.studio.client.workbench.commands.Commands;
import org.rstudio.studio.client.workbench.events.LastChanceSaveEvent;
import org.rstudio.studio.client.workbench.events.SessionInitEvent;
import org.rstudio.studio.client.workbench.model.Agreement;
import org.rstudio.studio.client.workbench.model.Session;
import org.rstudio.studio.client.workbench.model.SessionInfo;
import org.rstudio.studio.client.workbench.model.SessionUtils;
import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
import org.rstudio.studio.client.workbench.views.source.editors.text.themes.AceThemes;

@Singleton
public class Application implements ApplicationEventHandlers {
    public interface Binder extends CommandBinder<Commands, Application> {
    }

    @Inject
    public Application(ApplicationView view, GlobalDisplay globalDisplay, EventBus events, Binder binder,
            Commands commands, Server server, Session session, Projects projects, SatelliteManager satelliteManager,
            ApplicationUncaughtExceptionHandler uncaughtExHandler, Provider<UIPrefs> uiPrefs,
            Provider<Workbench> workbench, Provider<EventBus> eventBusProvider,
            Provider<ClientStateUpdater> clientStateUpdater, Provider<ApplicationClientInit> pClientInit,
            Provider<ApplicationQuit> pApplicationQuit, Provider<ApplicationInterrupt> pApplicationInterrupt,
            Provider<AceThemes> pAceThemes) {
        // save references
        view_ = view;
        globalDisplay_ = globalDisplay;
        events_ = events;
        session_ = session;
        commands_ = commands;
        satelliteManager_ = satelliteManager;
        clientStateUpdater_ = clientStateUpdater;
        server_ = server;
        uiPrefs_ = uiPrefs;
        workbench_ = workbench;
        eventBusProvider_ = eventBusProvider;
        pClientInit_ = pClientInit;
        pApplicationQuit_ = pApplicationQuit;
        pApplicationInterrupt_ = pApplicationInterrupt;
        pAceThemes_ = pAceThemes;

        // bind to commands
        binder.bind(commands_, this);

        // register as main window
        satelliteManager.initialize();

        // subscribe to events
        events.addHandler(LogoutRequestedEvent.TYPE, this);
        events.addHandler(UnauthorizedEvent.TYPE, this);
        events.addHandler(ReloadEvent.TYPE, this);
        events.addHandler(QuitEvent.TYPE, this);
        events.addHandler(SuicideEvent.TYPE, this);
        events.addHandler(SessionAbendWarningEvent.TYPE, this);
        events.addHandler(SessionSerializationEvent.TYPE, this);
        events.addHandler(ServerUnavailableEvent.TYPE, this);
        events.addHandler(InvalidClientVersionEvent.TYPE, this);
        events.addHandler(ServerOfflineEvent.TYPE, this);

        // register for uncaught exceptions
        uncaughtExHandler.register();
    }

    public void go(final RootLayoutPanel rootPanel, final Command dismissLoadingProgress) {
        Widget w = view_.getWidget();
        rootPanel.add(w);
        rootPanel.setWidgetTopBottom(w, 0, Style.Unit.PX, 0, Style.Unit.PX);
        rootPanel.setWidgetLeftRight(w, 0, Style.Unit.PX, 0, Style.Unit.PX);

        // attempt init
        pClientInit_.get().execute(new ServerRequestCallback<SessionInfo>() {

            public void onResponseReceived(final SessionInfo sessionInfo) {
                // initialize workbench after verifying agreement
                verifyAgreement(sessionInfo, new Operation() {
                    public void execute() {
                        dismissLoadingProgress.execute();

                        session_.setSessionInfo(sessionInfo);

                        // hide the workbench if we have a project parameter 
                        // (since we are going to redirect anyway)
                        if (haveProjectParameter())
                            hideWorkbench(rootPanel);

                        // initialize workbench
                        initializeWorkbench();

                        // reload application if we have a project parameter
                        if (haveProjectParameter())
                            reloadApplication(sessionInfo.getSwitchToProject());
                    }
                });
            }

            public void onError(ServerError error) {
                Debug.logError(error);
                dismissLoadingProgress.execute();

                globalDisplay_.showErrorMessage("RStudio Initialization Error", error.getUserMessage());
            }
        });
    }

    private void reloadApplication(final String switchToProject) {
        // use a last chance save barrier since we typically call this very
        // early in the lifetime of the application before client/server
        // sync has occurred
        Barrier barrier = new Barrier();
        barrier.addBarrierReleasedHandler(new BarrierReleasedHandler() {
            @Override
            public void onBarrierReleased(BarrierReleasedEvent event) {
                if (switchToProject.length() > 0)
                    pApplicationQuit_.get().forceSwitchProject(switchToProject);
                else
                    reloadWindowWithDelay(true);
            }
        });

        Token token = barrier.acquire();
        try {
            events_.fireEvent(new LastChanceSaveEvent(barrier));
        } finally {
            token.release();
        }
    }

    @Handler
    public void onShowToolbar() {
        setToolbarPref(true);
    }

    @Handler
    public void onHideToolbar() {
        setToolbarPref(false);
    }

    @Handler
    void onShowAboutDialog() {
        server_.getProductInfo(new ServerRequestCallback<ProductInfo>() {
            @Override
            public void onResponseReceived(ProductInfo info) {
                AboutDialog about = new AboutDialog(info);
                about.showModal();
            }

            @Override
            public void onError(ServerError error) {
            }
        });
    }

    @Handler
    public void onGoToFileFunction() {
        view_.performGoToFunction();
    }

    public void onUnauthorized(UnauthorizedEvent event) {
        navigateToSignIn();
    }

    public void onServerOffline(ServerOfflineEvent event) {
        cleanupWorkbench();
        view_.showApplicationOffline();
    }

    public void onLogoutRequested(LogoutRequestedEvent event) {
        navigateWindowTo("auth-sign-out");
    }

    @Handler
    public void onHelpUsingRStudio() {
        String customDocsURL = session_.getSessionInfo().docsURL();
        if (customDocsURL.length() > 0)
            globalDisplay_.openWindow(customDocsURL);
        else
            globalDisplay_.openRStudioLink("docs");
    }

    private void showAgreement() {
        globalDisplay_.openWindow(server_.getApplicationURL("agreement"));
    }

    @Handler
    public void onRstudioSupport() {
        globalDisplay_.openRStudioLink("support");
    }

    @Handler
    public void onRstudioAgreement() {
        showAgreement();
    }

    @Handler
    public void onUpdateCredentials() {
        server_.updateCredentials();
    }

    @Handler
    public void onRaiseException() {
        throw new RuntimeException("foo");
    }

    @Handler
    public final native void onRaiseException2() /*-{
                                                 $wnd.welfkjweg();
                                                 }-*/;

    @Handler
    public void onShowRequestLog() {
        GWT.runAsync(new RunAsyncCallback() {
            public void onFailure(Throwable reason) {
                Window.alert(reason.toString());
            }

            public void onSuccess() {
                final RequestLogVisualization viz = new RequestLogVisualization();
                final RootLayoutPanel root = RootLayoutPanel.get();
                root.add(viz);
                root.setWidgetTopBottom(viz, 10, Unit.PX, 10, Unit.PX);
                root.setWidgetLeftRight(viz, 10, Unit.PX, 10, Unit.PX);
                viz.addCloseHandler(new CloseHandler<RequestLogVisualization>() {
                    public void onClose(CloseEvent<RequestLogVisualization> event) {
                        root.remove(viz);
                    }
                });
            }
        });
    }

    @Handler
    public void onLogFocusedElement() {
        Element el = DomUtils.getActiveElement();
        DomUtils.dump(el, "Focused Element: ");
    }

    @Handler
    public void onRefreshSuperDevMode() {
        SuperDevMode.reload();
    }

    @Handler
    public void onZoomActualSize() {
        // only supported in cocoa desktop
        if (BrowseCap.isCocoaDesktop())
            Desktop.getFrame().macZoomActualSize();
    }

    @Handler
    public void onZoomIn() {
        // pass on to cocoa desktop (qt desktop intercepts)
        if (BrowseCap.isCocoaDesktop())
            Desktop.getFrame().macZoomIn();
    }

    @Handler
    public void onZoomOut() {
        // pass on to cocoa desktop (qt desktop intercepts)
        if (BrowseCap.isCocoaDesktop())
            Desktop.getFrame().macZoomOut();
    }

    public void onSessionSerialization(SessionSerializationEvent event) {
        switch (event.getAction().getType()) {
        case SessionSerializationAction.LOAD_DEFAULT_WORKSPACE:
            view_.showSerializationProgress("Loading workspace" + getSuffix(event), false, // non-modal, appears to user as std latency
                    500, // willing to show progress earlier since
                    // this will always be at workbench startup
                    0); // no timeout
            break;
        case SessionSerializationAction.SAVE_DEFAULT_WORKSPACE:
            view_.showSerializationProgress("Saving workspace image" + getSuffix(event), true, // modal, inputs will fall dead anyway
                    0, // show immediately
                    0); // no timeout
            break;
        case SessionSerializationAction.SUSPEND_SESSION:
            view_.showSerializationProgress("Backing up R session...", true, // modal, inputs will fall dead anyway
                    0, // show immediately
                    60000); // timeout after 60 seconds. this is done
                            // in case the user suspends or loses
                            // connectivity during the backup (in which
                            // case the 'completed' event dies with
                            // server and is never received by the client
            break;
        case SessionSerializationAction.RESUME_SESSION:
            view_.showSerializationProgress("Resuming R session...", false, // non-modal, appears to user as std latency
                    2000, // don't show this for reasonable restore time
                    // (happens inline while using a running
                    // workbench so be more conservative)
                    0); // no timeout
            break;
        case SessionSerializationAction.COMPLETED:
            view_.hideSerializationProgress();
            break;
        }
    }

    private String getSuffix(SessionSerializationEvent event) {
        SessionSerializationAction action = event.getAction();
        String targetPath = action.getTargetPath();
        if (targetPath != null) {
            String verb = " from ";
            if (action.getType() == SessionSerializationAction.SAVE_DEFAULT_WORKSPACE)
                verb = " to ";
            return verb + targetPath + "...";
        } else {
            return "...";
        }
    }

    public void onServerUnavailable(ServerUnavailableEvent event) {
        view_.hideSerializationProgress();
    }

    public void onReload(ReloadEvent event) {
        cleanupWorkbench();

        reloadWindowWithDelay(false);
    }

    public void onQuit(QuitEvent event) {
        cleanupWorkbench();

        // only show the quit state in server mode (desktop mode has its
        // own handling triggered to process exit)
        if (!Desktop.isDesktop()) {
            // if we are switching projects then reload after a delay (to allow
            // the R session to fully exit on the server)
            if (event.getSwitchProjects()) {
                reloadWindowWithDelay(true);
            } else {
                view_.showApplicationQuit();
            }
        }
    }

    private void reloadWindowWithDelay(final boolean baseUrlOnly) {
        new Timer() {
            @Override
            public void run() {
                if (baseUrlOnly)
                    Window.Location.replace(GWT.getHostPageBaseURL());
                else
                    Window.Location.reload();
            }
        }.schedule(100);
    }

    public void onSuicide(SuicideEvent event) {
        cleanupWorkbench();
        view_.showApplicationSuicide(event.getMessage());
    }

    public void onClientDisconnected(ClientDisconnectedEvent event) {
        cleanupWorkbench();
        view_.showApplicationDisconnected();
    }

    public void onInvalidClientVersion(InvalidClientVersionEvent event) {
        cleanupWorkbench();
        view_.showApplicationUpdateRequired();
    }

    public void onSessionAbendWarning(SessionAbendWarningEvent event) {
        view_.showSessionAbendWarning();
    }

    private void verifyAgreement(SessionInfo sessionInfo, final Operation verifiedOperation) {
        // get the agreement (if any)
        final Agreement agreement = sessionInfo.pendingAgreement();

        // if there is an agreement then prompt user for agreement (otherwise just
        // execute the verifiedOperation immediately)
        if (agreement != null) {
            // append updated to the title if necessary
            String title = agreement.getTitle();
            if (agreement.getUpdated())
                title += " (Updated)";

            view_.showApplicationAgreement(

                    // title and contents   
                    title, agreement.getContents(),

                    // bail to sign in page if the user doesn't confirm
                    new Operation() {
                        public void execute() {
                            if (Desktop.isDesktop()) {
                                Desktop.getFrame().setPendingQuit(DesktopFrame.PENDING_QUIT_AND_EXIT);
                                server_.quitSession(false, null, new SimpleRequestCallback<Boolean>());
                            } else
                                navigateToSignIn();
                        }
                    },

                    // user confirmed
                    new Operation() {
                        public void execute() {
                            // call verified operation
                            verifiedOperation.execute();

                            // record agreement on server
                            server_.acceptAgreement(agreement, new VoidServerRequestCallback());
                        }
                    }

            );

        } else {
            // no agreement pending
            verifiedOperation.execute();
        }
    }

    private void navigateWindowTo(String relativeUrl) {
        cleanupWorkbench();

        String url = GWT.getHostPageBaseURL() + relativeUrl;
        Window.Location.replace(url);
    }

    private void initializeWorkbench() {
        pAceThemes_.get();

        // subscribe to ClientDisconnected event (wait to do this until here
        // because there were spurious ClientDisconnected events occuring
        // after a session interrupt sequence. we couldn't figure out why,
        // and since this is a temporary hack why not add another temporary
        // hack to go with it here :-)
        // TOOD: move this back tot he constructor after we revise the
        // interrupt hack(s)
        events_.addHandler(ClientDisconnectedEvent.TYPE, this);

        // create workbench
        Workbench wb = workbench_.get();
        eventBusProvider_.get().fireEvent(new SessionInitEvent());

        // disable commands
        SessionInfo sessionInfo = session_.getSessionInfo();
        if (!sessionInfo.getAllowShell()) {
            commands_.showShellDialog().remove();
        }
        if (!sessionInfo.getAllowPackageInstallation()) {
            commands_.installPackage().remove();
            commands_.updatePackages().remove();
        }
        if (!sessionInfo.getAllowVcs()) {
            commands_.versionControlProjectSetup().remove();
        }
        if (!sessionInfo.getAllowFileDownloads()) {
            commands_.exportFiles().remove();
        }

        // disable external publishing if requested
        if (!SessionUtils.showExternalPublishUi(session_, uiPrefs_.get())) {
            commands_.publishHTML().remove();
            commands_.publishPlotToRPubs().remove();
            commands_.presentationPublishToRpubs().remove();
            commands_.viewerPublishToRPubs().remove();
        }

        // hide the agreement menu item if we don't have one
        if (!session_.getSessionInfo().hasAgreement())
            commands_.rstudioAgreement().setVisible(false);

        // show workbench
        view_.showWorkbenchView(wb.getMainView().asWidget());

        // hide zoom actual size everywhere but cocoa desktop
        if (!BrowseCap.isCocoaDesktop()) {
            commands_.zoomActualSize().remove();
        }

        // hide zoom in and zoom out in web mode
        if (!Desktop.isDesktop()) {
            commands_.zoomIn().remove();
            commands_.zoomOut().remove();
        }

        // toolbar (must be after call to showWorkbenchView because
        // showing the toolbar repositions the workbench view widget)
        showToolbar(uiPrefs_.get().toolbarVisible().getValue());

        // sync to changes in the toolbar visibility state
        uiPrefs_.get().toolbarVisible().addValueChangeHandler(new ValueChangeHandler<Boolean>() {
            @Override
            public void onValueChange(ValueChangeEvent<Boolean> event) {
                showToolbar(event.getValue());
            }
        });

        clientStateUpdaterInstance_ = clientStateUpdater_.get();
    }

    private void setToolbarPref(boolean showToolbar) {
        uiPrefs_.get().toolbarVisible().setGlobalValue(showToolbar);
        uiPrefs_.get().writeUIPrefs();
    }

    private void showToolbar(boolean showToolbar) {
        // show or hide the toolbar
        view_.showToolbar(showToolbar);

        // manage commands
        commands_.showToolbar().setVisible(!showToolbar);
        commands_.hideToolbar().setVisible(showToolbar);
    }

    private void hideWorkbench(final RootLayoutPanel rootPanel) {
        final Label w = new Label();
        w.getElement().getStyle().setBackgroundColor("#e1e2e5");
        rootPanel.add(w);
        rootPanel.setWidgetTopBottom(w, 0, Style.Unit.PX, 0, Style.Unit.PX);
        rootPanel.setWidgetLeftRight(w, 0, Style.Unit.PX, 0, Style.Unit.PX);
    }

    private void cleanupWorkbench() {
        server_.disconnect();

        satelliteManager_.closeAllSatellites();

        if (clientStateUpdaterInstance_ != null) {
            clientStateUpdaterInstance_.suspend();
            clientStateUpdaterInstance_ = null;
        }
    }

    private void navigateToSignIn() {
        navigateWindowTo("auth-sign-in");
    }

    private boolean haveProjectParameter() {
        return Window.Location.getParameter("project") != null;
    }

    private final ApplicationView view_;
    private final GlobalDisplay globalDisplay_;
    private final EventBus events_;
    private final Session session_;
    private final Commands commands_;
    private final SatelliteManager satelliteManager_;
    private final Provider<ClientStateUpdater> clientStateUpdater_;
    private final Server server_;
    private final Provider<UIPrefs> uiPrefs_;
    private final Provider<Workbench> workbench_;
    private final Provider<EventBus> eventBusProvider_;
    private final Provider<ApplicationClientInit> pClientInit_;
    private final Provider<ApplicationQuit> pApplicationQuit_;
    private final Provider<ApplicationInterrupt> pApplicationInterrupt_;
    private final Provider<AceThemes> pAceThemes_;

    private ClientStateUpdater clientStateUpdaterInstance_;
}