com.atlassian.theplugin.idea.config.ProjectConfigurationComponent.java Source code

Java tutorial

Introduction

Here is the source code for com.atlassian.theplugin.idea.config.ProjectConfigurationComponent.java

Source

/**
 * Copyright (C) 2008 Atlassian
 *
 * 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.atlassian.theplugin.idea.config;

import com.atlassian.connector.cfg.ProjectCfgManager;
import com.atlassian.connector.intellij.bamboo.IntelliJBambooServerFacade;
import com.atlassian.connector.intellij.fisheye.IntelliJFishEyeServerFacade;
import com.atlassian.theplugin.commons.ServerType;
import com.atlassian.theplugin.commons.UiTaskExecutor;
import com.atlassian.theplugin.commons.bamboo.BambooServerData;
import com.atlassian.theplugin.commons.cfg.BambooServerCfg;
import com.atlassian.theplugin.commons.cfg.ConfigurationListenerAdapter;
import com.atlassian.theplugin.commons.cfg.JiraServerCfg;
import com.atlassian.theplugin.commons.cfg.PrivateConfigurationDao;
import com.atlassian.theplugin.commons.cfg.PrivateProjectConfiguration;
import com.atlassian.theplugin.commons.cfg.PrivateServerCfgInfo;
import com.atlassian.theplugin.commons.cfg.ProjectConfiguration;
import com.atlassian.theplugin.commons.cfg.ServerCfg;
import com.atlassian.theplugin.commons.cfg.ServerCfgFactoryException;
import com.atlassian.theplugin.commons.cfg.ServerIdImpl;
import com.atlassian.theplugin.commons.cfg.xstream.JDomProjectConfigurationDao;
import com.atlassian.theplugin.commons.cfg.xstream.UserSharedConfigurationDao;
import com.atlassian.theplugin.commons.jira.IntelliJJiraServerFacade;
import com.atlassian.theplugin.commons.jira.JiraServerData;
import com.atlassian.theplugin.commons.remoteapi.ServerData;
import com.atlassian.theplugin.configuration.WorkspaceConfigurationBean;
import com.atlassian.theplugin.idea.IdeaHelper;
import com.atlassian.theplugin.idea.IdeaVersionFacade;
import com.atlassian.theplugin.idea.ui.DialogWithDetails;
import com.atlassian.theplugin.util.PluginUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.components.SettingsSavingComponent;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.vfs.VirtualFile;
import org.apache.commons.io.FileUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.jdom.xpath.XPath;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.Icon;
import javax.swing.JComponent;
import java.awt.EventQueue;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;

public class ProjectConfigurationComponent implements ProjectComponent, SettingsSavingComponent, Configurable {

    private final Project project;
    private final ProjectManager projectManager;
    private final ProjectCfgManagerImpl projectCfgManager;
    private final UiTaskExecutor uiTaskExecutor;
    private final PrivateConfigurationDao privateCfgDao;
    private final UserSharedConfigurationDao userSharedConfigurationDao;
    private final WorkspaceConfigurationBean projectConfigurationBean;
    private static final String CFG_LOAD_ERROR_MSG = "Error while loading the configuration of "
            + PluginUtil.PRODUCT_NAME;
    private static final Icon PLUGIN_SETTINGS_ICON = IconLoader.getIcon("/icons/ico_plugin.png");
    private ProjectConfigurationPanel projectConfigurationPanel;
    private final LocalConfigurationListener configurationListener = new LocalConfigurationListener();
    /**
     * race condtions wrt to this variable are harmless as threads mutating it (via save) reset it from false to true
     * and then save configuration. So saving (if really needed) will not be skipped anyway even if two threads
     * start in the moment when shouldSaveConfiguration was false
     */
    private boolean shouldSaveConfiguration;
    private ServerData selectedServer;

    public ProjectConfigurationComponent(final Project project, final ProjectManager projectManager,
            final ProjectCfgManager projectCfgManager, final UiTaskExecutor uiTaskExecutor,
            @NotNull PrivateConfigurationDao privateCfgDao,
            @NotNull WorkspaceConfigurationBean projectConfigurationBean,
            @NotNull UserSharedConfigurationDao sharedCfgDao) {
        this.project = project;
        this.projectManager = projectManager;
        this.projectCfgManager = (ProjectCfgManagerImpl) projectCfgManager;
        this.uiTaskExecutor = uiTaskExecutor;
        this.privateCfgDao = privateCfgDao;
        this.userSharedConfigurationDao = sharedCfgDao;
        this.projectConfigurationBean = projectConfigurationBean;
        //      shouldSaveConfiguration = load();
    }

    public static void handleServerCfgFactoryException(final Project theProject, final Exception e) {
        ApplicationManager.getApplication().invokeLater(new Runnable() {
            public void run() {
                DialogWithDetails.showExceptionDialog(theProject,
                        CFG_LOAD_ERROR_MSG + "\nEmpty configuration will be used.\n"
                                + "If you want to preserve settings stored in your configuration, do not edit your "
                                + PluginUtil.PRODUCT_NAME
                                + " configuration using IDEA, but instead close the project "
                                + "and try resolve the problem by modyfing directly the configuration file",
                        e);
            }
        });
    }

    public void projectOpened() {
        shouldSaveConfiguration = load();
        if (projectCfgManager.getProjectConfiguration() == null) {
            ApplicationManager.getApplication().invokeLater(new Runnable() {
                public void run() {
                    Messages.showErrorDialog(project, "If you see this message, something bad happend to the "
                            + "initialization sequence of IDEA. You may encounter now various strange problems with Connector."
                            + "\nPlease report occurence of this message to us.",
                            "Internal Error in Atlassian Connector");
                }
            });
        }

        projectCfgManager.addProjectConfigurationListener(configurationListener);
    }

    public void projectClosed() {
        projectCfgManager.removeProjectConfigurationListener(configurationListener);
    }

    @NonNls
    @NotNull
    public String getComponentName() {
        return ProjectConfigurationComponent.class.getSimpleName();
    }

    public void initComponent() {
    }

    public void disposeComponent() {
    }

    public boolean load() {
        migrateFileFromProjectRootToIdeaDirectory();

        Element rootElement = new Element("atlassian-ide-plugin");
        Element pc = new Element("project-configuration");
        pc.getChildren().add(new Element("servers"));
        rootElement.getChildren().add(pc);
        Document root = new Document(rootElement);
        final SAXBuilder builder = new SAXBuilder(false);
        //      final ProjectId projectId = CfgUtil.getProjectId(project);
        try {
            final String path = getCfgFilePath();
            final File file = path != null ? new File(path) : null;
            if (path == null || !file.exists()) {
                // this is an empty project (default template used by IDEA)
                setDefaultProjectConfiguration();
            } else {
                FileInputStream inStream = new FileInputStream(file);
                root = builder.build(inStream);
                cleanupDom(root);
            }
        } catch (Exception e) {
            handleServerCfgFactoryException(project, e);
            setDefaultProjectConfiguration();
            return false;
        }

        final JDomProjectConfigurationDao cfgFactory = new JDomProjectConfigurationDao(root.getRootElement(),
                privateCfgDao, userSharedConfigurationDao);
        migrateOldPrivateProjectSettings(cfgFactory);

        try {
            final ProjectConfiguration projectConfiguration;
            projectConfiguration = cfgFactory.load();

            PasswordStorage.loadPasswordsFromSecureStore(project, null /*projectManager.getDefaultProject()*/,
                    projectConfiguration, cfgFactory);

            if (projectConfiguration.getDefaultFishEyeServer() == null) {
                //means that configuration holds Crucible as FishEye server.
                //in the future this code should be removed
                //now resolves migration problem from Crucible as FishEye to pure FishEye
                projectConfiguration.setDefaultFishEyeServerId(null);
            }
            projectCfgManager.updateProjectConfiguration(projectConfiguration);
        } catch (ServerCfgFactoryException e) {
            handleServerCfgFactoryException(project, e);
            setDefaultProjectConfiguration();
            return false;
        }

        return true;
    }

    private void migrateOldPrivateProjectSettings(final JDomProjectConfigurationDao cfgFactory) {
        final SAXBuilder builder = new SAXBuilder(false);
        final File privateCfgFile = getPrivateOldCfgFilePath();
        boolean someMigrationHappened = false;
        if (privateCfgFile != null) {
            try {
                final Document privateRoot = builder.build(privateCfgFile);
                final PrivateProjectConfiguration ppc = cfgFactory
                        .loadOldPrivateConfiguration(privateRoot.getRootElement());
                for (PrivateServerCfgInfo privateServerCfgInfo : ppc.getPrivateServerCfgInfos()) {
                    try {
                        final PrivateServerCfgInfo newPsci = privateCfgDao.load(privateServerCfgInfo.getServerId());
                        if (newPsci == null) {
                            privateCfgDao.save(privateServerCfgInfo);
                            someMigrationHappened = true;
                        }
                    } catch (ServerCfgFactoryException e) {
                        // ignore here - just don't try to overwrite it with data from old XML file
                    }
                }
            } catch (Exception e) {
                handleServerCfgFactoryException(project, e);
            }
        }

        if (someMigrationHappened) {
            ApplicationManager.getApplication().invokeLater(new Runnable() {
                public void run() {
                    int value = Messages.showYesNoCancelDialog(project,
                            "Configuration has been succesfully migrated to new location (home directory).\n"
                                    + "Would you like to delete the old configuration file?\n\nDelete file: ["
                                    + privateCfgFile + "]",
                            PluginUtil.PRODUCT_NAME + " upgrade process", Messages.getQuestionIcon());

                    if (value == DialogWrapper.OK_EXIT_CODE) {
                        if (!privateCfgFile.delete()) {
                            Messages.showWarningDialog(project,
                                    "Cannot remove file [" + privateCfgFile.getAbsolutePath()
                                            + "].\nTry removing it manually.\n" + PluginUtil.PRODUCT_NAME
                                            + " should still behave correctly.",
                                    PluginUtil.PRODUCT_NAME);
                        }

                    }
                }
            });
        }
    }

    private void migrateFileFromProjectRootToIdeaDirectory() {
        boolean migrationHappened = false;

        final File cfgFileInProjectRoot = new File(getCfgFilePathInProjectRootDirectory());
        final File newCfgFile = new File(getCfgFilePath());

        // note that this will always be false when using file-based project configuration, as both paths are equal
        if (cfgFileInProjectRoot.exists() && !newCfgFile.exists()) {
            try {
                FileUtils.moveFile(cfgFileInProjectRoot, newCfgFile);
                migrationHappened = true;
            } catch (IOException e) {
                handleServerCfgFactoryException(project, e);
            }
        }

        if (migrationHappened) {
            ApplicationManager.getApplication().invokeLater(new Runnable() {
                public void run() {
                    Messages.showInfoMessage(project,
                            "The Atlassian IDE Connector configuration file has been moved from "
                                    + getCfgFilePathInProjectRootDirectory() + " to " + getCfgFilePath()
                                    + ". If it was under version control, please make "
                                    + "the appropriate changes to reflect this.",
                            PluginUtil.PRODUCT_NAME + " upgrade process");
                }
            });
        }
    }

    /**
     * Ensuring that old attributes do not break our loading
     *
     * @param root root element of XML document
     * @throws org.jdom.JDOMException when tree is invalid
     */
    private void cleanupDom(final Document root) throws JDOMException {
        @SuppressWarnings("unchecked")
        final List<Element> prjCfgNode = XPath.selectNodes(root, "atlassian-ide-plugin/project-configuration");
        for (Element e : prjCfgNode) {
            e.removeChild("isPrivateConfigurationMigrated");
            e.removeChild("defaultJiraServerId");
            e.removeChild("defaultJiraProject");
        }
        /** there was defaultUser (by mistake) child of server element */
        @SuppressWarnings("unchecked")
        List<Element> serverNodes = XPath.selectNodes(root, "atlassian-ide-plugin/project-configuration/servers/*");
        for (Element e : serverNodes) {
            e.removeChild("defaultUser");
        }

        @SuppressWarnings("unchecked")
        List<Element> projectConfigurationNode = XPath.selectNodes(root,
                "atlassian-ide-plugin/project-configuration");
        for (Element e : projectConfigurationNode) {
            e.removeChild("defaultUser");
        }

        /* remove unused anymore BambooServerCfg fields */
        @SuppressWarnings("unchecked")
        List<Element> bambooNodes = XPath.selectNodes(root,
                "atlassian-ide-plugin/project-configuration/servers/bamboo");
        for (Element e : bambooNodes) {
            e.removeChild("isBamboo2M9");
            e.removeChild("isBamboo24");
        }
    }

    private ProjectConfiguration setDefaultProjectConfiguration() {
        final ProjectConfiguration configuration = ProjectConfiguration.emptyConfiguration();
        projectCfgManager.updateProjectConfiguration(configuration);
        return configuration;
    }

    @Nullable
    private String getCfgFilePathInProjectRootDirectory() {
        final VirtualFile baseDir = project.getBaseDir();
        if (baseDir == null) {
            return null;
        }
        return baseDir.getPath() + File.separator + "atlassian-ide-plugin.xml";
    }

    @Nullable
    private String getCfgFilePath() {
        final VirtualFile baseDir = project.getBaseDir();
        if (baseDir == null) {
            return null;
        }
        File dotIdeaDirectory = new File(baseDir.getPath() + File.separator + ".idea");
        if (dotIdeaDirectory.exists() && dotIdeaDirectory.isDirectory()) {
            return baseDir.getPath() + File.separator + ".idea" + File.separator + "atlassian-ide-plugin.xml";
        } else {
            return getCfgFilePathInProjectRootDirectory();
        }
    }

    private File getPrivateOldCfgFilePath() {
        final VirtualFile baseDir = project.getBaseDir();
        if (baseDir == null) {
            return null;
        }
        final File baseNewProjectFile = new File(
                baseDir.getPath() + File.separator + "atlassian-ide-plugin.private.xml");

        if (baseNewProjectFile.isFile() && baseNewProjectFile.canRead()) {
            return baseNewProjectFile;
        }

        return null;
    }

    //   private ProjectId getProjectId() {
    //      return CfgUtil.getProjectId(project);
    //   }

    public void save() {
        final Element element = new Element("atlassian-ide-plugin");

        JDomProjectConfigurationDao cfgFactory = new JDomProjectConfigurationDao(element, privateCfgDao,
                userSharedConfigurationDao);
        final ProjectConfiguration configuration = projectCfgManager.getProjectConfiguration();
        if (configuration != null) {
            if (configuration.getServers().size() > 0 && !shouldSaveConfiguration) {
                // apparently somebody still prefers to populate invalid configuration, so we would save it now
                shouldSaveConfiguration = true;
            }

            if (shouldSaveConfiguration) {

                PasswordStorage.savePasswordsToSecureStore(project, null /*projectManager.getDefaultProject()*/,
                        configuration, cfgFactory);

                final String publicCfgFile = getCfgFilePath();

                writeXmlFile(element, publicCfgFile);

            }
        }
    }

    private void writeXmlFile(final Element element, final String filepath) {
        if (filepath == null) {
            return; // handlig for instance default dummy project
        }
        try {
            final FileWriter writer = new FileWriter(filepath);

            final XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
            //outputter.setLineSeparator(System.getProperty("line.separator"));
            outputter.output(element, writer);
            writer.close();
        } catch (IOException e) {
            Messages.showWarningDialog(project,
                    "Cannot save project configuration settings to [" + filepath + ":\n" + e.getMessage(), "Error");
        }
    }

    @Nls
    public String getDisplayName() {
        return IdeaVersionFacade.getInstance().getSettingsMenuProjectDisplayString();
    }

    @Nullable
    public Icon getIcon() {
        return PLUGIN_SETTINGS_ICON;
    }

    @Nullable
    @NonNls
    public String getHelpTopic() {
        return null;
    }

    public JComponent createComponent() {
        ProjectConfiguration configuration = projectCfgManager.getProjectConfiguration();
        if (configuration == null) {
            // may happen for Default Template project
            configuration = setDefaultProjectConfiguration();
        }

        projectConfigurationPanel = new ProjectConfigurationPanel(project, configuration.getClone(),
                IntelliJFishEyeServerFacade.getInstance(),
                IntelliJBambooServerFacade.getInstance(PluginUtil.getLogger()),
                IntelliJJiraServerFacade.getInstance(), uiTaskExecutor, selectedServer,
                projectCfgManager.getDefaultCredentials().getClone(), projectCfgManager.isDefaultCredentialsAsked(),
                projectConfigurationBean);
        return projectConfigurationPanel;
    }

    public boolean isModified() {
        projectConfigurationPanel.saveData(false);
        return !(projectCfgManager.getProjectConfiguration()
                .equals(projectConfigurationPanel.getProjectConfiguration())
                && projectCfgManager.getDefaultCredentials()
                        .equals(projectConfigurationPanel.getDefaultCredentials())
                && projectCfgManager.isDefaultCredentialsAsked() == projectConfigurationPanel
                        .isDefaultCredentialsAsked());
    }

    public void apply() throws ConfigurationException {
        projectConfigurationPanel.askForDefaultCredentials();
        if (projectConfigurationPanel == null) {
            return;
        }
        projectConfigurationPanel.saveData(true);
        projectCfgManager.updateProjectConfiguration(projectConfigurationPanel.getProjectConfiguration());
        projectConfigurationPanel.setData(projectCfgManager.getProjectConfiguration().getClone());
        projectCfgManager.setDefaultCredentials(projectConfigurationPanel.getDefaultCredentials());
        projectCfgManager.setDefaultCredentialsAsked(projectConfigurationPanel.isDefaultCredentialsAsked());
    }

    public void updateConfiguration(final ProjectConfiguration projectConfiguration) {
        projectCfgManager.updateProjectConfiguration(projectConfiguration);
        if (projectConfigurationPanel != null) {
            projectConfigurationPanel.setData(projectCfgManager.getProjectConfiguration().getClone());
        }
    }

    public ProjectConfiguration getProjectConfigurationClone() {
        return projectCfgManager.getProjectConfiguration().getClone();
    }

    // pstefaniak, 21 jan 2010: this should probably go outside of this class... to some kind of helper class... dunno
    public static void fireDirectClickedServerPopup(final Project project, final String serverUrl,
            final ServerType serverType, final Runnable runnable) {
        /*      final Color BACKGROUND_COLOR = new Color(255, 255, 200);
            
              StringBuilder sb = new StringBuilder("Server <i>" + serverUrl + "</i> not found in configuration<br>");
              sb.append("<br>Click on this notification to open configuration panel and add this server");
            
              JEditorPane content = new JEditorPane();
              content.setEditable(false);
              content.setContentType("text/html");
              content.setEditorKit(new ClasspathHTMLEditorKit());
              content.setText("<html>" + Constants.BODY_WITH_STYLE + sb.toString() + "</body></html>");
              content.setBackground(BACKGROUND_COLOR);
              content.addHyperlinkListener(new GenericHyperlinkListener());
            
              content.addMouseListener(new MouseAdapter() {
                 @Override
                 public void mouseClicked(MouseEvent e) {
        if (ProjectConfigurationComponent.addDirectClickedServer(project, serverUrl, serverType)) {
           EventQueue.invokeLater(runnable);
        }
                 }
              });
            
              IdeaVersionFacade.getInstance().fireNotification(
        project,
        new JScrollPane(content),
        content.getText(),
        "/icons/crucible-blue-16.png",
        IdeaVersionFacade.OperationStatus.INFO,
        BACKGROUND_COLOR);*/

        if (Messages.showYesNoDialog(
                "Server " + serverUrl + " not found in configuration,\ndo you want to "
                        + "open configuration panel and add this server?",
                "Server not found", Messages.getQuestionIcon()) == DialogWrapper.OK_EXIT_CODE) {
            if (ProjectConfigurationComponent.addDirectClickedServer(project, serverUrl, serverType)) {
                EventQueue.invokeLater(runnable);
            }
        }
    }

    /*
     * Method for adding auto-filled server configuration based on parameters passed by directClickThroughRequest
     *
     * @return true if user clicked accept, false if clicked cancel
     * @remark pstefaniak, 21 jan 2010: this should probably go outside of this class... to some kind of helper class... dunno
     */
    public static boolean addDirectClickedServer(final Project project, final String serverUrl,
            ServerType serverType) {
        ProjectConfigurationComponent component = project.getComponent(ProjectConfigurationComponent.class);

        ProjectConfiguration configurationClone = component.getProjectConfigurationClone();

        ServerIdImpl id = new ServerIdImpl();
        String name = serverUrl;
        if (name.contains("://")) {
            name = name.substring(name.indexOf("://") + 3);
            if (name.contains("/")) {
                name = name.substring(0, name.indexOf("/"));
            }
        }
        ServerCfg serverCfg = null;
        final ServerData data;
        //beautiful switch: :/
        switch (serverType) {
        case BAMBOO_SERVER:
            serverCfg = new BambooServerCfg(name, id);
            data = new BambooServerData((BambooServerCfg) serverCfg);
            break;
        case JIRA_SERVER:
            serverCfg = new JiraServerCfg(name, id, true);
            data = new JiraServerData(serverCfg);
            break;
        default:
            throw new AssertionError("switch not implemented for [" + serverType + "]");
        }

        serverCfg.setUrl(serverUrl);
        serverCfg.setUseDefaultCredentials(true);

        configurationClone.getServers().add(serverCfg);
        component.updateConfiguration(configurationClone);

        component.setSelectedServer(data);

        final ShowSettingsUtil settingsUtil = ShowSettingsUtil.getInstance();
        if (settingsUtil != null) {
            boolean hasClickedOkButton = settingsUtil.editConfigurable(project, component);
            if (!hasClickedOkButton) {
                configurationClone = component.getProjectConfigurationClone();
                ServerCfg toRemoveCfg = configurationClone.getServerCfg(id);
                configurationClone.getServers().remove(toRemoveCfg);
                component.updateConfiguration(configurationClone);
            }
            return hasClickedOkButton;
        }
        return false;
    }

    public void reset() {
    }

    public void disposeUIResources() {
        projectConfigurationPanel = null;
    }

    public void setSelectedServer(final ServerData server) {
        this.selectedServer = server;
    }

    private class LocalConfigurationListener extends ConfigurationListenerAdapter {
        @Override
        public void configurationUpdated(ProjectConfiguration aProjectConfiguration) {
            save();
            IdeaHelper.getAppComponent().rescheduleStatusCheckers(true);
        }
    }
}