org.codinjutsu.tools.jenkins.view.BrowserPanel.java Source code

Java tutorial

Introduction

Here is the source code for org.codinjutsu.tools.jenkins.view.BrowserPanel.java

Source

/*
 * Copyright (c) 2013 David Boissier
 *
 * 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.codinjutsu.tools.jenkins.view;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.SimpleToolWindowPanel;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.ui.BrowserHyperlinkListener;
import com.intellij.ui.PopupHandler;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.TreeSpeedSearch;
import com.intellij.ui.treeStructure.SimpleTree;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.containers.Convertor;
import com.intellij.util.ui.tree.TreeUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.codinjutsu.tools.jenkins.JenkinsAppSettings;
import org.codinjutsu.tools.jenkins.JenkinsSettings;
import org.codinjutsu.tools.jenkins.JenkinsWindowManager;
import org.codinjutsu.tools.jenkins.exception.ConfigurationException;
import org.codinjutsu.tools.jenkins.logic.*;
import org.codinjutsu.tools.jenkins.model.*;
import org.codinjutsu.tools.jenkins.util.GuiUtil;
import org.codinjutsu.tools.jenkins.view.action.*;
import org.codinjutsu.tools.jenkins.view.action.settings.SortByStatusAction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class BrowserPanel extends SimpleToolWindowPanel implements Disposable {

    private static final Logger logger = Logger.getLogger(BrowserPanel.class);

    private static final String UNAVAILABLE = "No Jenkins server available";

    private static final String LOADING = "Loading...";

    private JPanel rootPanel;

    private JPanel jobPanel;
    private final Tree jobTree;

    private boolean sortedByBuildStatus;

    private final Runnable refreshViewJob;
    private ScheduledFuture<?> refreshViewFutureTask;

    private final Project project;

    private final JenkinsAppSettings jenkinsAppSettings;
    private final JenkinsSettings jenkinsSettings;

    private final RequestManager requestManager;

    private final Jenkins jenkins;
    private FavoriteView favoriteView;
    private View currentSelectedView;

    private final Map<String, Job> watchedJobs = new ConcurrentHashMap<String, Job>();

    private static final Comparator<DefaultMutableTreeNode> sortByStatusComparator = new Comparator<DefaultMutableTreeNode>() {
        @Override
        public int compare(DefaultMutableTreeNode treeNode1, DefaultMutableTreeNode treeNode2) {
            Job job1 = ((Job) treeNode1.getUserObject());
            Job job2 = ((Job) treeNode2.getUserObject());

            return new Integer(BuildStatusEnum.getStatus(job1.getColor()).ordinal())
                    .compareTo(BuildStatusEnum.getStatus(job2.getColor()).ordinal());
        }
    };
    private static final Comparator<DefaultMutableTreeNode> sortByNameComparator = new Comparator<DefaultMutableTreeNode>() {
        @Override
        public int compare(DefaultMutableTreeNode treeNode1, DefaultMutableTreeNode treeNode2) {
            Job job1 = ((Job) treeNode1.getUserObject());
            Job job2 = ((Job) treeNode2.getUserObject());

            return job1.getName().compareTo(job2.getName());
        }
    };

    public static BrowserPanel getInstance(Project project) {
        return ServiceManager.getService(project, BrowserPanel.class);
    }

    public BrowserPanel(final Project project) {

        super(true);
        this.project = project;

        refreshViewJob = new Runnable() {
            @Override
            public void run() {
                GuiUtil.runInSwingThread(new LoadSelectedViewJob(project));
            }
        };

        requestManager = RequestManager.getInstance(project);
        jenkinsAppSettings = JenkinsAppSettings.getSafeInstance(project);
        jenkinsSettings = JenkinsSettings.getSafeInstance(project);
        setProvideQuickActions(false);

        jenkins = Jenkins.byDefault();
        jobTree = createTree(jenkinsSettings.getFavoriteJobs());

        jobPanel.setLayout(new BorderLayout());
        jobPanel.add(ScrollPaneFactory.createScrollPane(jobTree), BorderLayout.CENTER);

        setContent(rootPanel);
    }

    /*whole method could be moved inside of ExecutorProvider (executor would expose interface that would allow to schedule
      new task previously cancelling previous ones) */
    public void initScheduledJobs() {
        final ExecutorService executorService = ExecutorService.getInstance(project);
        final ScheduledThreadPoolExecutor executor = executorService.getExecutor();
        executorService.safeTaskCancel(refreshViewFutureTask);
        executor.remove(refreshViewJob);

        if (jenkinsAppSettings.isServerUrlSet() && jenkinsAppSettings.getJobRefreshPeriod() > 0) {
            refreshViewFutureTask = executor.scheduleWithFixedDelay(refreshViewJob,
                    jenkinsAppSettings.getJobRefreshPeriod(), jenkinsAppSettings.getJobRefreshPeriod(),
                    TimeUnit.MINUTES);
        }
    }

    public Job getSelectedJob() {
        DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) jobTree.getLastSelectedPathComponent();
        if (treeNode != null) {
            Object userObject = treeNode.getUserObject();
            if (userObject instanceof Job) {
                return (Job) userObject;
            }
        }
        return null;
    }

    public List<Job> getAllSelectedJobs() {
        return TreeUtil.collectSelectedObjectsOfType(jobTree, Job.class);
    }

    public List<Job> getJobs() {
        return jenkins.getJobs();
    }

    public Job getJob(String name) {
        List<Job> jobs = jenkins.getJobs();
        if (jobs.size() > 0) {
            for (Job job : jobs) {
                if (job.getName().equals(name)) {
                    return job;
                }
            }
        }
        return null;
    }

    public void setSortedByStatus(boolean selected) {
        sortedByBuildStatus = selected;
        final DefaultTreeModel model = (DefaultTreeModel) jobTree.getModel();
        if (selected) {
            TreeUtil.sort(model, sortByStatusComparator);
        } else {
            TreeUtil.sort(model, sortByNameComparator);
        }

        GuiUtil.runInSwingThread(new Runnable() {
            @Override
            public void run() {
                model.nodeStructureChanged((TreeNode) model.getRoot());
            }
        });
    }

    @Override
    public void dispose() {
        ToolWindowManager.getInstance(project).unregisterToolWindow(JenkinsWindowManager.JENKINS_BROWSER);
    }

    private static void visit(Job job, BuildStatusVisitor buildStatusVisitor) {
        Build lastBuild = job.getLastBuild();
        if (job.isBuildable() && lastBuild != null) {
            BuildStatusEnum status = lastBuild.getStatus();
            if (BuildStatusEnum.FAILURE == status) {
                buildStatusVisitor.visitFailed();
                return;
            }
            if (BuildStatusEnum.SUCCESS == status) {
                buildStatusVisitor.visitSuccess();
                return;
            }
            if (BuildStatusEnum.UNSTABLE == status) {
                buildStatusVisitor.visitUnstable();
                return;
            }
            if (BuildStatusEnum.ABORTED == status) {
                buildStatusVisitor.visitAborted();
                return;
            }
            if (BuildStatusEnum.NULL == status) {
                buildStatusVisitor.visitUnknown();
                return;
            }
        }

        buildStatusVisitor.visitUnknown();
    }

    private void update() {
        ((DefaultTreeModel) jobTree.getModel())
                .nodeChanged((TreeNode) jobTree.getSelectionPath().getLastPathComponent());
    }

    public Jenkins getJenkins() {
        return jenkins;
    }

    public View getCurrentSelectedView() {
        return currentSelectedView;
    }

    public RequestManager getJenkinsManager() {
        return requestManager;
    }

    public void loadSelectedJob() {
        if (SwingUtilities.isEventDispatchThread()) {
            logger.warn("BrowserPanel.loadSelectedJob called from EDT");
        }
        final Job job = getSelectedJob();
        loadJob(job);

    }

    public void loadJob(final Job job) {
        if (!SwingUtilities.isEventDispatchThread()) {
            logger.warn("BrowserPanel.loadJob called from outside of EDT");
        }
        GuiUtil.runInSwingThread(
                new Task.Backgroundable(project, "Loading job", true, JenkinsLoadingTaskOption.INSTANCE) {

                    private Job returnJob;

                    @Override
                    public void run(@NotNull ProgressIndicator indicator) {
                        returnJob = requestManager.loadJob(job);
                    }

                    @Override
                    public void onSuccess() {
                        job.updateContentWith(returnJob);
                        updateJobNode(job);
                    }

                });
    }

    private void updateJobNode(Job job) {
        final DefaultTreeModel model = (DefaultTreeModel) jobTree.getModel();
        final Object modelRoot = model.getRoot();
        final int childCount = model.getChildCount(modelRoot);
        for (int i = 0; i < childCount; ++i) {
            Object child = model.getChild(modelRoot, i);
            if (child instanceof DefaultMutableTreeNode) {
                DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) child;
                if (childNode.getUserObject() == job) {
                    model.nodeChanged(childNode);
                    break;
                }
            }
        }
    }

    public boolean hasFavoriteJobs() {
        return !jenkinsSettings.getFavoriteJobs().isEmpty();
    }

    public void notifyInfoJenkinsToolWindow(final String htmlLinkMessage) {
        ToolWindowManager.getInstance(project).notifyByBalloon(JenkinsWindowManager.JENKINS_BROWSER,
                MessageType.INFO, htmlLinkMessage, null, new BrowserHyperlinkListener());
    }

    public void notifyErrorJenkinsToolWindow(final String message) {
        GuiUtil.runInSwingThread(new Runnable() {
            @Override
            public void run() {
                ToolWindowManager.getInstance(project).notifyByBalloon(JenkinsWindowManager.JENKINS_BROWSER,
                        MessageType.ERROR, message);
            }
        });
    }

    private Tree createTree(final List<JenkinsSettings.FavoriteJob> favoriteJobs) {

        SimpleTree tree = new SimpleTree();
        tree.getEmptyText().setText(LOADING);
        tree.setCellRenderer(new JenkinsTreeRenderer(favoriteJobs));
        tree.setName("jobTree");
        tree.setModel(new DefaultTreeModel(new DefaultMutableTreeNode(jenkins)));

        new TreeSpeedSearch(tree, new Convertor<TreePath, String>() {

            @Override
            public String convert(TreePath treePath) {
                final DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath.getLastPathComponent();
                final Object userObject = node.getUserObject();
                if (userObject instanceof Job) {
                    return ((Job) userObject).getName();
                }
                return "<empty>";
            }
        });

        return tree;
    }

    public void handleEmptyConfiguration() {
        JenkinsWidget.getInstance(project).updateStatusIcon(BuildStatusAggregator.EMPTY); //FIXME could be handled elsehwere
        DefaultTreeModel model = (DefaultTreeModel) jobTree.getModel();
        DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
        root.removeAllChildren();
        model.nodeStructureChanged(root);
        jobTree.setRootVisible(false);

        jenkins.update(Jenkins.byDefault());

        currentSelectedView = null;
        setJobsUnavailable();
    }

    public void setJobsUnavailable() {
        jobTree.getEmptyText().setText(UNAVAILABLE);
    }

    public void postAuthenticationInitialization() {
        if (!jenkinsSettings.getFavoriteJobs().isEmpty()) {
            createFavoriteViewIfNecessary();
        }

        String lastSelectedViewName = jenkinsSettings.getLastSelectedView();
        View viewToLoad;
        if (StringUtils.isEmpty(lastSelectedViewName)) {
            viewToLoad = jenkins.getPrimaryView();
        } else if (favoriteView != null && lastSelectedViewName.equals(favoriteView.getName())) {
            viewToLoad = favoriteView;
        } else {
            viewToLoad = jenkins.getViewByName(lastSelectedViewName);
        }
        loadView(viewToLoad);
    }

    public void init() {
        initGui();
        if (!isConfigured()) { //run when there is not configuration
            handleEmptyConfiguration();
        }
    }

    private void initGui() {
        installActionsInToolbar();
        installActionsInPopupMenu();
    }

    private void installActionsInToolbar() {
        DefaultActionGroup actionGroup = new DefaultActionGroup("JenkinsToolbarGroup", false);
        actionGroup.add(new SelectViewAction(this));
        actionGroup.add(new RefreshNodeAction(this));
        actionGroup.add(new RunBuildAction(this));
        actionGroup.add(new StopBuildAction(this));
        actionGroup.add(new SortByStatusAction(this));
        actionGroup.add(new RefreshRssAction());
        actionGroup.addSeparator();
        actionGroup.add(new OpenPluginSettingsAction());

        GuiUtil.installActionGroupInToolBar(actionGroup, this, ActionManager.getInstance(),
                "jenkinsBrowserActions");
    }

    private void installActionsInPopupMenu() {
        DefaultActionGroup popupGroup = new DefaultActionGroup("JenkinsPopupAction", true);

        popupGroup.add(new RunBuildAction(this));
        popupGroup.add(new StopBuildAction(this));
        //TODO add show log
        popupGroup.addSeparator();
        popupGroup.add(new SetJobAsFavoriteAction(this));

        popupGroup.add(new UnsetJobAsFavoriteAction(this));
        popupGroup.addSeparator();
        popupGroup.add(new GotoJobPageAction(this));
        popupGroup.add(new GotoLastBuildPageAction(this));
        popupGroup.addSeparator();
        popupGroup.add(new UploadPatchToJob(this));

        PopupHandler.installPopupHandler(jobTree, popupGroup, "POPUP", ActionManager.getInstance());
    }

    public void loadView(final View view) {
        this.currentSelectedView = view;
        if (!SwingUtilities.isEventDispatchThread()) {
            logger.warn("BrowserPanel.loadView called from outside EDT");
        }
        new LoadSelectedViewJob(project).queue();
    }

    public void refreshCurrentView() {
        if (!SwingUtilities.isEventDispatchThread()) {
            logger.warn("BrowserPanel.refreshCurrentView called outside EDT");
        }
        new LoadSelectedViewJob(project).queue();
    }

    private void loadJobs() {
        if (SwingUtilities.isEventDispatchThread()) {
            logger.warn("BrowserPanel.loadJobs called from EDT");
        }
        final List<Job> jobList;
        if (currentSelectedView instanceof FavoriteView) {
            jobList = requestManager.loadFavoriteJobs(jenkinsSettings.getFavoriteJobs());
        } else {
            jobList = requestManager.loadJenkinsView(currentSelectedView);
        }

        jenkinsSettings.setLastSelectedView(currentSelectedView.getName());

        jenkins.setJobs(jobList);
    }

    private View getViewToLoad() {
        if (currentSelectedView != null) {
            return currentSelectedView;
        }

        View primaryView = jenkins.getPrimaryView();
        if (primaryView != null) {
            return primaryView;
        }

        return null;
    }

    private void fillJobTree(final BuildStatusVisitor buildStatusVisitor) {
        final List<Job> jobList = jenkins.getJobs();
        if (jobList.isEmpty()) {
            return;
        }

        DefaultTreeModel model = (DefaultTreeModel) jobTree.getModel();
        final DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode) model.getRoot();
        rootNode.removeAllChildren();

        for (Job job : jobList) {
            DefaultMutableTreeNode jobNode = new DefaultMutableTreeNode(job);
            rootNode.add(jobNode);
            visit(job, buildStatusVisitor);
        }

        watch();

        setSortedByStatus(sortedByBuildStatus);

        jobTree.setRootVisible(true);
    }

    public void setAsFavorite(final List<Job> jobs) {
        jenkinsSettings.addFavorite(jobs);
        createFavoriteViewIfNecessary();
        update();
    }

    public void removeFavorite(final List<Job> selectedJobs) {
        jenkinsSettings.removeFavorite(selectedJobs);
        update();
        if (jenkinsSettings.isFavoriteViewEmpty() && currentSelectedView instanceof FavoriteView) {
            favoriteView = null;
            loadView(jenkins.getPrimaryView());
        } else {
            if (currentSelectedView instanceof FavoriteView) {
                loadView(currentSelectedView);
            }
        }
    }

    public boolean isAFavoriteJob(final String jobName) {
        return jenkinsSettings.isAFavoriteJob(jobName);
    }

    private void createFavoriteViewIfNecessary() {
        if (favoriteView == null) {
            favoriteView = FavoriteView.create();
        }
    }

    private void setTreeBusy(final boolean isBusy) {
        GuiUtil.runInSwingThread(new Runnable() {
            @Override
            public void run() {
                jobTree.setPaintBusy(isBusy);
            }
        });

    }

    public boolean isConfigured() {
        return jenkinsAppSettings.isServerUrlSet();
    }

    public void updateWorkspace(Jenkins jenkinsWorkspace) {
        jenkins.update(jenkinsWorkspace);
    }

    private class LoadSelectedViewJob extends Task.Backgroundable {
        public LoadSelectedViewJob(@Nullable Project project) {
            super(project, "Loading Jenkins Jobs", true, JenkinsLoadingTaskOption.INSTANCE);
        }

        @Override
        public void run(@NotNull ProgressIndicator indicator) {
            indicator.setIndeterminate(true);
            try {
                setTreeBusy(true);
                View viewToLoad = getViewToLoad();
                if (viewToLoad == null) {
                    return;
                }
                currentSelectedView = viewToLoad;
                loadJobs();

            } catch (ConfigurationException ex) {
                notifyErrorJenkinsToolWindow(ex.getMessage());
            } finally {
                setTreeBusy(false);
            }
        }

        @Override
        public void onSuccess() {
            final BuildStatusAggregator buildStatusAggregator = new BuildStatusAggregator(jenkins.getJobs().size());

            GuiUtil.runInSwingThread(new Runnable() {
                @Override
                public void run() {
                    fillJobTree(buildStatusAggregator);
                    JenkinsWidget.getInstance(project).updateStatusIcon(buildStatusAggregator);
                }
            });

        }
    }

    public void addToWatch(String changeListName, Job job) {
        JenkinsAppSettings settings = JenkinsAppSettings.getSafeInstance(project);
        Build build = job.getLastBuild();
        build.setNumber(build.getNumber() + 1);
        build.setUrl(settings.getServerUrl() + String.format("/job/%s/%d/", job.getName(), build.getNumber()));
        watchedJobs.put(changeListName, job);
    }

    private void watch() {
        if (!SwingUtilities.isEventDispatchThread()) {
            logger.warn("BrowserPanel.watch called from outside EDT");
        }
        if (!watchedJobs.isEmpty()) {
            for (final Map.Entry<String, Job> entry : watchedJobs.entrySet()) {
                final Job job = entry.getValue();
                final Build lastBuild = job.getLastBuild();
                new Task.Backgroundable(project, "Jenkins build watch", true, JenkinsLoadingTaskOption.INSTANCE) {

                    private Build build;

                    @Override
                    public void onSuccess() {
                        if (lastBuild.isBuilding() && !build.isBuilding()) {
                            notifyInfoJenkinsToolWindow(String.format("Status of build for Changelist \"%s\" is %s",
                                    entry, build.getStatus().getStatus()));
                        }
                        job.setLastBuild(build);
                    }

                    @Override
                    public void run(@NotNull ProgressIndicator indicator) {
                        indicator.setIndeterminate(true);
                        build = requestManager.loadBuild(lastBuild);
                    }
                }.queue();
            }
        }
    }

    public Map<String, Job> getWatched() {
        return watchedJobs;
    }
}