org.ow2.proactive_grid_cloud_portal.scheduler.client.SchedulerController.java Source code

Java tutorial

Introduction

Here is the source code for org.ow2.proactive_grid_cloud_portal.scheduler.client.SchedulerController.java

Source

/*
 * ################################################################
 *
 * ProActive Parallel Suite(TM): The Java(TM) library for
 *    Parallel, Distributed, Multi-Core Computing for
 *    Enterprise Grids & Clouds
 *
 * Copyright (C) 1997-2015 INRIA/University of
 *                 Nice-Sophia Antipolis/ActiveEon
 * Contact: proactive@ow2.org or contact@activeeon.com
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; version 3 of
 * the License.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * If needed, contact us to obtain a release under GPL Version 2 or 3
 * or a different license than the AGPL.
 *
 *  Initial developer(s):               The ProActive Team
 *                        http://proactive.inria.fr/team_members.htm
 *  Contributor(s):
 *
 * ################################################################
 * $$PROACTIVE_INITIAL_DEV$$
 */
package org.ow2.proactive_grid_cloud_portal.scheduler.client;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

import org.ow2.proactive_grid_cloud_portal.common.client.Controller;
import org.ow2.proactive_grid_cloud_portal.common.client.LoadingMessage;
import org.ow2.proactive_grid_cloud_portal.common.client.LoginPage;
import org.ow2.proactive_grid_cloud_portal.common.client.Settings;
import org.ow2.proactive_grid_cloud_portal.common.client.json.JSONUtils;
import org.ow2.proactive_grid_cloud_portal.common.client.model.LogModel;
import org.ow2.proactive_grid_cloud_portal.common.client.model.LoginModel;
import org.ow2.proactive_grid_cloud_portal.common.shared.Config;
import org.ow2.proactive_grid_cloud_portal.scheduler.client.controller.ExecutionsController;
import org.ow2.proactive_grid_cloud_portal.scheduler.client.controller.OutputController;
import org.ow2.proactive_grid_cloud_portal.scheduler.client.controller.ResultController;
import org.ow2.proactive_grid_cloud_portal.scheduler.client.controller.ServerLogsController;
import org.ow2.proactive_grid_cloud_portal.scheduler.client.controller.TasksController;
import org.ow2.proactive_grid_cloud_portal.scheduler.client.model.ExecutionsModel;
import org.ow2.proactive_grid_cloud_portal.scheduler.shared.SchedulerConfig;

import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONException;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.client.Cookies;
import com.google.gwt.user.client.Random;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.layout.Layout;
import com.smartgwt.client.widgets.layout.SectionStackSection;
import com.smartgwt.client.widgets.layout.VLayout;

/**
 * Logic that interacts between the remote Scheduler and the local Model
 * <p>
 * The Controller can be accessed statically by the client to ensure
 * coherent modification of the Model data:
 * <ul><li>views submit actions to the Controller,
 * <li>the Controller performs the actions,
 * <li>the Controller updates new Data to the Model,
 * <li>the view displays what it reads from the Model.
 * </code>
 *
 * @author mschnoor
 */
public class SchedulerController extends Controller implements UncaughtExceptionHandler {

    static final String LOCAL_SESSION_COOKIE = "pa.sched.local_session";

    private static final int AUTO_LOGIN_TIMER_PERIOD_IN_MS = 1000;

    @Override
    public String getLoginSettingKey() {
        return LOGIN_SETTING;
    }

    @Override
    public String getLogo350Url() {
        return SchedulerImagesUnbundled.LOGO_350;
    }

    /** if this is different than LOCAL_SESSION cookie, we need to disconnect */
    private String localSessionNum;

    /** scheduler server for async rpc calls */
    private SchedulerServiceAsync scheduler = null;

    /** writable view of the model */
    private SchedulerModelImpl model = null;

    /** login page when not logged in, or null */
    private LoginPage loginView = null;
    /** main page when logged in, or null */
    private SchedulerPage schedulerView = null;

    /** periodically updates the local job view */
    private Timer schedulerTimerUpdate = null;

    // incremented each time #fetchJobs is called
    private int timerUpdate = 0;

    /** frequency of user info fetch */
    private int userFetchTick = active_tick;
    /** frequency of stats info fetch */
    private int statsFetchTick = active_tick;

    /** do not fetch visualization info when false */
    private boolean visuFetchEnabled = false;

    private static final int active_tick = 3;
    private static final int lazy_tick = 20;

    private Timer autoLoginTimer;

    protected TasksController tasksController;

    protected ExecutionsController executionController;

    protected OutputController outputController;

    protected ServerLogsController serverLogsController;

    protected ResultController resultController;

    /**
     * Default constructor
     *
     * @param scheduler server endpoint for async remote calls
     */
    public SchedulerController(SchedulerServiceAsync scheduler) {
        this.scheduler = scheduler;
        this.model = new SchedulerModelImpl();

        init();
    }

    /**
     * Entry point: creates the GUI
     */
    private void init() {
        final String session = Settings.get().getSetting(SESSION_SETTING);

        if (session != null) {
            LoadingMessage loadingMessage = new LoadingMessage();
            loadingMessage.draw();
            tryLogin(session, loadingMessage);
        } else {
            this.loginView = new LoginPage(this, null);
            tryToLoginIfLoggedInRm();
        }
    }

    private void tryLogin(final String session, final VLayout loadingMessage) {
        this.scheduler.getSchedulerStatus(session, new AsyncCallback<String>() {
            public void onSuccess(String result) {
                if (loadingMessage != null) {
                    loadingMessage.destroy();
                }
                login(session, Settings.get().getSetting(LOGIN_SETTING));
                LogModel.getInstance().logMessage("Rebound session " + session);
            }

            public void onFailure(Throwable caught) {
                if (loadingMessage != null) {
                    loadingMessage.destroy();
                }
                Settings.get().clearSetting(SESSION_SETTING);
                SchedulerController.this.loginView = new LoginPage(SchedulerController.this, null);
                tryToLoginIfLoggedInRm();
            }
        });
    }

    private void tryToLoginIfLoggedInRm() {
        autoLoginTimer = new Timer() {
            @Override
            public void run() {
                String session = Settings.get().getSetting(SESSION_SETTING);
                if (session != null) {
                    tryLogin(session, null);
                }
            }
        };
        autoLoginTimer.scheduleRepeating(AUTO_LOGIN_TIMER_PERIOD_IN_MS);
    }

    private void stopTryingLoginIfLoggerInRm() {
        if (autoLoginTimer != null) {
            autoLoginTimer.cancel();
        }
    }

    /**
     * @return the Model used by this Controller
     */
    public SchedulerModel getModel() {
        return this.model;
    }

    /**
     * Use the event dispatcher to register listeners for specific events
     * 
     * @return the current event dispatcher
     */
    @Override
    public SchedulerEventDispatcher getEventDispatcher() {
        return this.model;
    }

    public SchedulerServiceAsync getScheduler() {
        return scheduler;
    }

    @Override
    public void login(final String sessionId, final String login) {
        stopTryingLoginIfLoggerInRm();
        scheduler.getVersion(new AsyncCallback<String>() {
            public void onSuccess(String result) {
                JSONObject obj = JSONParser.parseStrict(result).isObject();
                String schedVer = obj.get("scheduler").isString().stringValue();
                String restVer = obj.get("rest").isString().stringValue();
                Config.get().set(SchedulerConfig.SCHED_VERSION, schedVer);
                Config.get().set(SchedulerConfig.REST_VERSION, restVer);

                __login(sessionId, login);
            }

            public void onFailure(Throwable caught) {
                String msg = JSONUtils.getJsonErrorMessage(caught);
                LogModel.getInstance().logImportantMessage("Failed to get REST server version: " + msg);
            }
        });

    }

    private void __login(String sessionId, String login) {
        LoginModel loginModel = LoginModel.getInstance();
        loginModel.setLoggedIn(true);
        loginModel.setLogin(login);
        loginModel.setSessionId(sessionId);

        if (loginView != null)
            SchedulerController.this.loginView.destroy();
        this.loginView = null;
        this.schedulerView = new SchedulerPage(this);

        this.executionController.getJobsController().fetchJobs(true);

        this.startTimer();

        String lstr = "";
        if (login != null) {
            lstr += " as " + login;
        }

        Settings.get().setSetting(SESSION_SETTING, sessionId);
        if (login != null) {
            Settings.get().setSetting(LOGIN_SETTING, login);
        } else {
            Settings.get().clearSetting(LOGIN_SETTING);
        }

        // this cookie is reset to a random int on every login:
        // if another session in another tab has a different localSessionNUm
        // than the one in the domain cookie, then we exit
        this.localSessionNum = "" + System.currentTimeMillis() + "_" + Random.nextInt();
        Cookies.setCookie(LOCAL_SESSION_COOKIE, this.localSessionNum);

        LogModel.getInstance().logMessage("Connected to " + SchedulerConfig.get().getRestUrl() + lstr
                + " (sessionId=" + loginModel.getSessionId() + ")");
    }

    /**
     * Disconnect from the Scheduler
     * <p>
     * Maintains coherent state in the views between
     * LoginView and SchedulerView, depending the connection status
     *
     * @throws IllegalStateException not connected
     */
    public void logout() {
        if (!LoginModel.getInstance().isLoggedIn())
            throw new IllegalStateException("Not connected");

        Settings.get().clearSetting(SESSION_SETTING);

        scheduler.logout(LoginModel.getInstance().getSessionId(), new AsyncCallback<Void>() {

            public void onFailure(Throwable caught) {
            }

            public void onSuccess(Void result) {
            }

        });

        // do not wait for the callback, stop the thread immediately
        // or it may try to update stuff while disconnected
        teardown(null);
        tryToLoginIfLoggedInRm();
    }

    public Layout buildTaskView() {
        this.tasksController = new TasksController(this);
        return this.tasksController.buildView();
    }

    public SectionStackSection buildExecutionsView() {
        this.executionController = new ExecutionsController(this);
        return this.executionController.buildView();
    }

    public Layout buildOutputView() {
        this.outputController = new OutputController(this);
        return this.outputController.buildView();
    }

    public Layout buildServerLogsView() {
        this.serverLogsController = new ServerLogsController(this);
        return this.serverLogsController.buildView();
    }

    public Layout buildPreviewView() {
        this.resultController = new ResultController(this);
        return this.resultController.buildView();
    }

    void setVisuFetchEnabled(boolean b) {
        this.visuFetchEnabled = b;
    }

    public void visuFetch(final String jobId) {

        // fetch visu info
        if (jobId != null && this.visuFetchEnabled) {
            String curHtml = model.getJobHtml(jobId);
            if (curHtml != null) {
                // exists already, resetting it will trigger listeners
                model.setJobHtml(jobId, curHtml);
            } else {
                final long t = System.currentTimeMillis();
                this.scheduler.getJobHtml(LoginModel.getInstance().getSessionId(), jobId,
                        new AsyncCallback<String>() {
                            public void onSuccess(String result) {
                                model.setJobHtml(jobId, result);
                                LogModel.getInstance().logMessage("Fetched html for job " + jobId + " in "
                                        + (System.currentTimeMillis() - t) + " ms");
                            }

                            public void onFailure(Throwable caught) {
                                String msg = "Failed to fetch html for job " + jobId;
                                String json = JSONUtils.getJsonErrorMessage(caught);
                                if (json != null)
                                    msg += " : " + json;

                                LogModel.getInstance().logImportantMessage(msg);

                                // trying to load image
                                String curPath = model.getJobImagePath(jobId);
                                if (curPath != null) {
                                    // exists already, resetting it will trigger listeners
                                    model.setJobImagePath(jobId, curPath);
                                } else {
                                    final long t = System.currentTimeMillis();
                                    scheduler.getJobImage(LoginModel.getInstance().getSessionId(), jobId,
                                            new AsyncCallback<String>() {
                                                public void onSuccess(String result) {
                                                    model.setJobImagePath(jobId, result);
                                                    LogModel.getInstance()
                                                            .logMessage("Fetched image for job " + jobId + " in "
                                                                    + (System.currentTimeMillis() - t) + " ms");
                                                }

                                                public void onFailure(Throwable caught) {
                                                    String msg = "Failed to fetch image for job " + jobId;
                                                    String json = JSONUtils.getJsonErrorMessage(caught);
                                                    if (json != null)
                                                        msg += " : " + json;

                                                    LogModel.getInstance().logImportantMessage(msg);
                                                    model.visuUnavailable(jobId);
                                                }
                                            });
                                }
                            }
                        });
            }

        }
    }

    /**
     * Attempt to start the scheduler, might fail depending the server state/rights
     */
    public void startScheduler() {
        this.scheduler.startScheduler(LoginModel.getInstance().getSessionId(), new AsyncCallback<Boolean>() {
            public void onFailure(Throwable caught) {
                String msg = JSONUtils.getJsonErrorMessage(caught);
                warn("Could not start Scheduler:\n" + msg);
                LogModel.getInstance().logImportantMessage("Failed to start Scheduler: " + msg);
            }

            public void onSuccess(Boolean result) {
                LogModel.getInstance().logMessage("Scheduler started");
            }
        });
    }

    /**
     * Attempt to stop the scheduler, might fail depending server state/rights
     */
    public void stopScheduler() {
        this.scheduler.stopScheduler(LoginModel.getInstance().getSessionId(), new AsyncCallback<Boolean>() {
            public void onSuccess(Boolean result) {
                LogModel.getInstance().logMessage("Scheduler stopped");
            }

            public void onFailure(Throwable caught) {
                String msg = JSONUtils.getJsonErrorMessage(caught);
                warn("Could not stop Scheduler:\n" + msg);
                LogModel.getInstance().logImportantMessage("Failed to stop Scheduler: " + msg);
            }
        });
    }

    /**
     * Attempt to pause the scheduler, might fail depending server state/rights
     */
    public void pauseScheduler() {
        this.scheduler.pauseScheduler(LoginModel.getInstance().getSessionId(), new AsyncCallback<Boolean>() {
            public void onSuccess(Boolean result) {
                LogModel.getInstance().logMessage("Scheduler paused");
            }

            public void onFailure(Throwable caught) {
                String msg = JSONUtils.getJsonErrorMessage(caught);
                warn("Could not pause Scheduler:\n" + msg);
                LogModel.getInstance().logImportantMessage("Failed to pause Scheduler: " + msg);
            }
        });
    }

    /**
     * Attempt to freeze the scheduler, might fail depending server state/rights
     */
    public void freezeScheduler() {
        this.scheduler.freezeScheduler(LoginModel.getInstance().getSessionId(), new AsyncCallback<Boolean>() {
            public void onSuccess(Boolean result) {
                LogModel.getInstance().logMessage("Scheduler freezed");
            }

            public void onFailure(Throwable caught) {
                String msg = JSONUtils.getJsonErrorMessage(caught);
                warn("Could not freeze Scheduler:\n" + msg);
                LogModel.getInstance().logImportantMessage("Failed to freeze Scheduler: " + msg);
            }
        });
    }

    /**
     * Attempt to resume the scheduler, might fail depending server state/rights
     */
    public void resumeScheduler() {
        this.scheduler.resumeScheduler(LoginModel.getInstance().getSessionId(), new AsyncCallback<Boolean>() {
            public void onSuccess(Boolean result) {
                LogModel.getInstance().logMessage("Scheduler resumed");
            }

            public void onFailure(Throwable caught) {
                String msg = JSONUtils.getJsonErrorMessage(caught);
                warn("Could not resume Scheduler:\n" + msg);
                LogModel.getInstance().logImportantMessage("Failed to resume Scheduler: " + msg);
            }
        });
    }

    /**
     * Attempt to kill the scheduler, might fail depending server state/rights
     */
    public void killScheduler() {
        this.scheduler.killScheduler(LoginModel.getInstance().getSessionId(), new AsyncCallback<Boolean>() {
            public void onSuccess(Boolean result) {
                LogModel.getInstance().logMessage("Scheduler killed");
            }

            public void onFailure(Throwable caught) {
                String msg = JSONUtils.getJsonErrorMessage(caught);
                warn("Could not kill Scheduler:\n" + msg);
                LogModel.getInstance().logImportantMessage("Failed to kill Scheduler: " + msg);
            }
        });
    }

    public OutputController getOutputController() {
        return outputController;
    }

    public void restartTimer() {
        this.stopTimer();
        this.startTimer();
    }

    /**
     * Starts the Timer that will periodically fetch the current scheduler state
     * from the server end and update the local view
     */
    private void startTimer() {
        if (this.schedulerTimerUpdate != null)
            throw new IllegalStateException("There's already a Timer");

        this.schedulerTimerUpdate = new Timer() {

            @Override
            public void run() {

                if (!localSessionNum.equals(Cookies.getCookie(LOCAL_SESSION_COOKIE))) {
                    teardown("Duplicate session detected!<br>"
                            + "Another tab or window in this browser is accessing this page.");
                }

                SchedulerController.this.updateSchedulerStatus();

                executionController.executionStateRevision(false);

                if (timerUpdate % userFetchTick == 0) {
                    final long t1 = System.currentTimeMillis();

                    scheduler.getSchedulerUsers(LoginModel.getInstance().getSessionId(),
                            new AsyncCallback<String>() {
                                public void onSuccess(String result) {
                                    List<SchedulerUser> users;

                                    JSONValue val = parseJSON(result);
                                    JSONArray arr = val.isArray();
                                    if (arr == null) {
                                        error("Expected JSON Array: " + val.toString());
                                    }
                                    users = getUsersFromJson(arr);
                                    model.setSchedulerUsers(users);

                                    long t = (System.currentTimeMillis() - t1);
                                    LogModel.getInstance().logMessage("<span style='color:gray;'>Fetched "
                                            + users.size() + " users in " + t + " ms</span>");
                                }

                                public void onFailure(Throwable caught) {
                                    if (!LoginModel.getInstance().isLoggedIn())
                                        return;

                                    error("Failed to fetch scheduler users:<br>"
                                            + JSONUtils.getJsonErrorMessage(caught));
                                }
                            });
                }

                if (timerUpdate % statsFetchTick == 0) {
                    final long t1 = System.currentTimeMillis();

                    scheduler.getStatistics(LoginModel.getInstance().getSessionId(), new AsyncCallback<String>() {
                        public void onFailure(Throwable caught) {
                            String msg = JSONUtils.getJsonErrorMessage(caught);
                            if (!LoginModel.getInstance().isLoggedIn())
                                return;
                            error("Failed to fetch scheduler stats:<br>" + msg);
                        }

                        public void onSuccess(String result) {
                            HashMap<String, String> stats = new HashMap<String, String>();

                            JSONObject json = parseJSON(result).isObject();
                            if (json == null)
                                error("Expected JSON Object: " + result);

                            stats.put("JobSubmittingPeriod",
                                    json.get("JobSubmittingPeriod").isString().stringValue());
                            stats.put("FormattedJobSubmittingPeriod",
                                    json.get("FormattedJobSubmittingPeriod").isString().stringValue());
                            stats.put("MeanJobPendingTime",
                                    json.get("MeanJobPendingTime").isString().stringValue());
                            stats.put("ConnectedUsersCount",
                                    json.get("ConnectedUsersCount").isString().stringValue());
                            stats.put("FinishedTasksCount",
                                    json.get("FinishedTasksCount").isString().stringValue());
                            stats.put("RunningJobsCount", json.get("RunningJobsCount").isString().stringValue());
                            stats.put("RunningTasksCount", json.get("RunningTasksCount").isString().stringValue());
                            stats.put("FormattedMeanJobPendingTime",
                                    json.get("FormattedMeanJobPendingTime").isString().stringValue());
                            stats.put("MeanJobExecutionTime",
                                    json.get("MeanJobExecutionTime").isString().stringValue());
                            stats.put("PendingTasksCount", json.get("PendingTasksCount").isString().stringValue());
                            stats.put("FinishedJobsCount", json.get("FinishedJobsCount").isString().stringValue());
                            stats.put("TotalTasksCount", json.get("TotalTasksCount").isString().stringValue());
                            stats.put("FormattedMeanJobExecutionTime",
                                    json.get("FormattedMeanJobExecutionTime").isString().stringValue());
                            stats.put("TotalJobsCount", json.get("TotalJobsCount").isString().stringValue());
                            stats.put("PendingJobsCount", json.get("PendingJobsCount").isString().stringValue());

                            model.setSchedulerStatistics(stats);

                            long t = (System.currentTimeMillis() - t1);
                            LogModel.getInstance().logMessage("<span style='color:gray;'>Fetched sched stats: "
                                    + result.length() + " chars in " + t + " ms</span>");
                        }
                    });

                    final long t2 = System.currentTimeMillis();

                    scheduler.getStatisticsOnMyAccount(LoginModel.getInstance().getSessionId(),
                            new AsyncCallback<String>() {
                                public void onFailure(Throwable caught) {
                                    if (!LoginModel.getInstance().isLoggedIn())
                                        return;
                                    error("Failed to fetch account stats:<br>"
                                            + JSONUtils.getJsonErrorMessage(caught));
                                }

                                public void onSuccess(String result) {
                                    HashMap<String, String> stats = new HashMap<String, String>();

                                    JSONObject json = parseJSON(result).isObject();
                                    if (json == null)
                                        error("Expected JSON Object: " + result);

                                    stats.put("TotalTaskCount",
                                            json.get("TotalTaskCount").isString().stringValue());
                                    stats.put("TotalJobDuration",
                                            json.get("TotalJobDuration").isString().stringValue());
                                    stats.put("TotalJobCount", json.get("TotalJobCount").isString().stringValue());
                                    stats.put("TotalTaskDuration",
                                            json.get("TotalTaskDuration").isString().stringValue());

                                    model.setAccountStatistics(stats);

                                    long t = (System.currentTimeMillis() - t2);
                                    LogModel.getInstance()
                                            .logMessage("<span style='color:gray;'>Fetched account stats: "
                                                    + result.length() + " chars in " + t + " ms</span>");
                                }
                            });
                }
                timerUpdate++;
            }
        };
        this.schedulerTimerUpdate.scheduleRepeating(SchedulerConfig.get().getClientRefreshTime());
    }

    /**
     * Parse the raw JSON array describing the users list, return a Java representation
     * @param jsonarray JSONArray containing all users
     * @return the currently connected users
     * @throws JSONException JSON parsing failed
     */
    private List<SchedulerUser> getUsersFromJson(JSONArray jsonarray) throws JSONException {
        ArrayList<SchedulerUser> users = new ArrayList<SchedulerUser>();

        for (int i = 0; i < jsonarray.size(); i++) {
            JSONObject jsonUser = jsonarray.get(i).isObject();
            users.add(SchedulerUser.parseJson(jsonUser));
        }

        return users;
    }

    /**
     * Fetch scheduler status, update the model,
     * fail hard on error
     */
    private void updateSchedulerStatus() {
        scheduler.getSchedulerStatus(LoginModel.getInstance().getSessionId(), new AsyncCallback<String>() {

            public void onFailure(Throwable caught) {
                if (!LoginModel.getInstance().isLoggedIn()) {
                    // might have been disconnected in between
                    return;
                }
                String msg = JSONUtils.getJsonErrorMessage(caught);
                error("Error while fetching status:\n" + caught.getClass().getName() + " " + msg);
                LogModel.getInstance().logImportantMessage("Error while fetching status: " + msg);
            }

            public void onSuccess(String result) {
                JSONValue val = parseJSON(result);
                String sval = val.isString().stringValue();
                SchedulerStatus stat = SchedulerStatus.valueOf(sval);
                SchedulerController.this.model.setSchedulerStatus(stat);

                if (result.equals(SchedulerStatus.SHUTTING_DOWN)) {
                    error("The Scheduler has been shut down, exiting");
                } else if (result.equals(SchedulerStatus.KILLED)) {
                    error("The Scheduler has been killed, exiting");
                }
                // do not model.logMessage() : this is repeated by a timer
            }

        });
    }

    /**
     * @param b true fetch users info less often
     */
    protected void setLazyUserFetch(boolean b) {
        this.userFetchTick = (b) ? lazy_tick : active_tick;
    }

    /**
     * @param b true fetch stats info less often
     */
    protected void setLazyStatsFetch(boolean b) {
        this.statsFetchTick = (b) ? lazy_tick : active_tick;
    }

    /**
     * Issue an error message to the user and exit the schedulerView
     *
     * @param reason error message to display
     */
    private void error(String reason) {
        LogModel.getInstance().logCriticalMessage(reason);
    }

    private void warn(String reason) {
        SC.warn(reason);
    }

    /**
     * Leave the scheduler view
     * Does not disconnect from the server
     */
    public void teardown(String message) {
        this.stopTimer();
        this.outputController.stopLiveOutput();
        this.model = new SchedulerModelImpl();

        SchedulerController.this.schedulerView.destroy();
        SchedulerController.this.schedulerView = null;

        this.loginView = new LoginPage(this, message);
    }

    /**
     * Stops the Timer that updates the local view of the remote scheduler
     */
    public void stopTimer() {
        if (this.schedulerTimerUpdate == null)
            return;

        this.schedulerTimerUpdate.cancel();
        this.schedulerTimerUpdate = null;
    }

    public void onUncaughtException(Throwable e) {
        e.printStackTrace();
    }

    public void getUsage(String user, Date startDate, Date endDate) {
        scheduler.getUsage(LoginModel.getInstance().getSessionId(), user, startDate, endDate,
                new AsyncCallback<List<JobUsage>>() {
                    @Override
                    public void onFailure(Throwable caught) {
                        String msg = JSONUtils.getJsonErrorMessage(caught);
                        LogModel.getInstance().logImportantMessage("Failed to fetch usage data " + ": " + msg);
                    }

                    @Override
                    public void onSuccess(List<JobUsage> jobUsages) {
                        model.setUsage(jobUsages);
                        LogModel.getInstance()
                                .logMessage("Successfully fetched usage for " + jobUsages.size() + " jobs");

                    }
                });
    }

    public void getUsersWithJobs() {
        final long t1 = System.currentTimeMillis();

        scheduler.getSchedulerUsersWithJobs(LoginModel.getInstance().getSessionId(), new AsyncCallback<String>() {
            public void onSuccess(String result) {
                List<SchedulerUser> users;

                JSONValue val = parseJSON(result);
                JSONArray arr = val.isArray();
                if (arr == null) {
                    error("Expected JSON Array: " + val.toString());
                }
                users = getUsersFromJson(arr);
                model.setSchedulerUsersWithJobs(users);

                long t = (System.currentTimeMillis() - t1);
                LogModel.getInstance().logMessage("<span style='color:gray;'>Fetched " + users.size()
                        + " users with jobs in " + t + " ms</span>");
            }

            public void onFailure(Throwable caught) {
                if (!LoginModel.getInstance().isLoggedIn())
                    return;

                LogModel.getInstance().logMessage(
                        "Failed to fetch scheduler users with jobs:<br>" + JSONUtils.getJsonErrorMessage(caught));
            }
        });
    }

    public void putThirdPartyCredential(final String key, String value) {
        scheduler.putThirdPartyCredential(LoginModel.getInstance().getSessionId(), key, value,
                new AsyncCallback<Void>() {

                    @Override
                    public void onFailure(Throwable caught) {
                        String msg = JSONUtils.getJsonErrorMessage(caught);
                        LogModel.getInstance()
                                .logImportantMessage("Error while saving third-party credential: " + msg);
                    }

                    @Override
                    public void onSuccess(Void result) {
                        LogModel.getInstance()
                                .logMessage("Successfully saved third-party credential for " + key + ".");
                        refreshThirdPartyCredentialsKeys();
                    }
                });
    }

    public void refreshThirdPartyCredentialsKeys() {
        scheduler.thirdPartyCredentialKeySet(LoginModel.getInstance().getSessionId(),
                new AsyncCallback<Set<String>>() {
                    @Override
                    public void onFailure(Throwable caught) {
                        String msg = JSONUtils.getJsonErrorMessage(caught);
                        LogModel.getInstance()
                                .logImportantMessage("Error while getting third-party credentials: " + msg);
                    }

                    @Override
                    public void onSuccess(Set<String> result) {
                        model.setThirdPartyCredentialsKeys(result);
                    }
                });
    }

    public void removeThirdPartyCredential(final String key) {
        scheduler.removeThirdPartyCredential(LoginModel.getInstance().getSessionId(), key,
                new AsyncCallback<Void>() {
                    @Override
                    public void onFailure(Throwable caught) {
                        String msg = JSONUtils.getJsonErrorMessage(caught);
                        LogModel.getInstance()
                                .logImportantMessage("Error while deleting third-party credential: " + msg);
                    }

                    @Override
                    public void onSuccess(Void result) {
                        LogModel.getInstance()
                                .logMessage("Successfully deleted third-party credential for " + key + ".");
                        refreshThirdPartyCredentialsKeys();
                    }
                });
    }

    public void resetPendingTasksRequests() {
        this.outputController.cancelCurrentRequests();
        this.tasksController.resetPendingTasksRequests();
    }

    public TasksController getTasksController() {
        return tasksController;
    }

    public void setTasksController(TasksController tasksController) {
        this.tasksController = tasksController;
    }

    public ExecutionsController getExecutionController() {
        return executionController;
    }

    public Task getSelectedTask() {
        ExecutionsModel executionsModel = this.model.getExecutionsModel();
        switch (executionsModel.getMode()) {
        case JOB_CENTRIC:
            return this.model.getTasksModel().getSelectedTask();
        case TASK_CENTRIC:
            return executionsModel.getTasksModel().getSelectedTask();
        default:
            return null;
        }
    }

    public Job getSelectedJob() {
        return this.model.getExecutionsModel().getSelectedJob();
    }
}