View Javadoc

1   /**
2    * Copyright (C) 2008 Atlassian
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.atlassian.theplugin.idea;
18  
19  import com.atlassian.theplugin.cfg.CfgUtil;
20  import com.atlassian.theplugin.commons.UIActionScheduler;
21  import com.atlassian.theplugin.commons.bamboo.*;
22  import com.atlassian.theplugin.commons.cfg.CfgManager;
23  import com.atlassian.theplugin.commons.cfg.ConfigurationListenerAdapter;
24  import com.atlassian.theplugin.commons.cfg.ProjectConfiguration;
25  import com.atlassian.theplugin.commons.configuration.PluginConfiguration;
26  import com.atlassian.theplugin.commons.crucible.CrucibleServerFacade;
27  import com.atlassian.theplugin.commons.crucible.CrucibleServerFacadeImpl;
28  import com.atlassian.theplugin.commons.util.LoggerImpl;
29  import com.atlassian.theplugin.configuration.WorkspaceConfigurationBean;
30  import com.atlassian.theplugin.crucible.model.CrucibleReviewListModel;
31  import com.atlassian.theplugin.idea.autoupdate.ConfirmPluginUpdateHandler;
32  import com.atlassian.theplugin.idea.autoupdate.PluginUpdateIcon;
33  import com.atlassian.theplugin.idea.bamboo.BambooStatusIcon;
34  import com.atlassian.theplugin.idea.bamboo.BuildListModelImpl;
35  import com.atlassian.theplugin.idea.bamboo.BuildStatusChangedToolTip;
36  import com.atlassian.theplugin.idea.config.ProjectCfgManagerImpl;
37  import com.atlassian.theplugin.idea.crucible.CruciblePatchSubmitExecutor;
38  import com.atlassian.theplugin.idea.crucible.CrucibleStatusChecker;
39  import com.atlassian.theplugin.idea.crucible.CrucibleStatusIcon;
40  import com.atlassian.theplugin.idea.crucible.editor.CrucibleEditorFactoryListener;
41  import com.atlassian.theplugin.idea.jira.IssueListToolWindowPanel;
42  import com.atlassian.theplugin.idea.ui.InformationDialogWithCheckBox;
43  import com.atlassian.theplugin.idea.ui.linkhiglighter.FileEditorListenerImpl;
44  import com.atlassian.theplugin.jira.model.JIRAIssueListModelBuilder;
45  import com.atlassian.theplugin.notification.crucible.CrucibleNotificationTooltip;
46  import com.atlassian.theplugin.notification.crucible.CrucibleReviewNotifier;
47  import com.atlassian.theplugin.remoteapi.MissingPasswordHandler;
48  import com.atlassian.theplugin.remoteapi.MissingPasswordHandlerQueue;
49  import com.atlassian.theplugin.util.PluginUtil;
50  import com.atlassian.theplugin.util.UsageStatisticsGenerator;
51  import com.intellij.ide.BrowserUtil;
52  import com.intellij.openapi.actionSystem.ActionManager;
53  import com.intellij.openapi.actionSystem.AnAction;
54  import com.intellij.openapi.actionSystem.DefaultActionGroup;
55  import com.intellij.openapi.application.ApplicationManager;
56  import com.intellij.openapi.application.ModalityState;
57  import com.intellij.openapi.components.ProjectComponent;
58  import com.intellij.openapi.editor.EditorFactory;
59  import com.intellij.openapi.project.Project;
60  import com.intellij.openapi.startup.StartupManager;
61  import com.intellij.openapi.ui.DialogWrapper;
62  import com.intellij.openapi.ui.Messages;
63  import com.intellij.openapi.util.IconLoader;
64  import com.intellij.openapi.vcs.changes.ChangeListManager;
65  import com.intellij.openapi.wm.ToolWindowManager;
66  import com.intellij.ui.content.ContentManagerAdapter;
67  import com.intellij.ui.content.ContentManagerEvent;
68  import com.jgoodies.forms.layout.CellConstraints;
69  import com.jgoodies.forms.layout.FormLayout;
70  import org.jetbrains.annotations.NotNull;
71  
72  import javax.swing.*;
73  import javax.swing.event.HyperlinkEvent;
74  import javax.swing.event.HyperlinkListener;
75  import java.util.Collection;
76  
77  /**
78   * Per-project plugin component.
79   */
80  
81  public class ThePluginProjectComponent implements ProjectComponent {
82  	private static final String THE_PLUGIN_TOOL_WINDOW_ICON = "/icons/ico_plugin_16.png";
83  
84  	private final WorkspaceConfigurationBean projectConfigurationBean;
85  	private final Project project;
86  
87  	public CfgManager getCfgManager() {
88  		return cfgManager;
89  	}
90  
91  	private final CfgManager cfgManager;
92  
93  	private final UIActionScheduler actionScheduler;
94  	private BambooStatusIcon statusBarBambooIcon;
95  	private CrucibleStatusIcon statusBarCrucibleIcon;
96  	private CrucibleNotificationTooltip crucibleTooltip;
97  
98  	private PluginUpdateIcon statusPluginUpdateIcon;
99  	private BambooStatusChecker bambooStatusChecker;
100 	private final BuildListModelImpl bambooModel;
101 	private CrucibleStatusChecker crucibleStatusChecker;
102 	private BambooStatusTooltipListener tooltipBambooStatusListener;
103 
104 	private final CrucibleServerFacade crucibleServerFacade;
105 
106 	private final ToolWindowManager toolWindowManager;
107 
108 	private boolean created;
109 	private CrucibleReviewNotifier crucibleReviewNotifier;
110 	private final CrucibleReviewListModel crucibleReviewListModel;
111 	private final ProjectCfgManagerImpl projectCfgManager;
112 	private final JIRAIssueListModelBuilder jiraIssueListModelBuilder;
113 	private final PluginConfiguration pluginConfiguration;
114 
115 	private IssueListToolWindowPanel issuesToolWindowPanel;
116 
117 	private PluginToolWindow toolWindow;
118 
119 	//	public static final Key<ReviewActionEventBroker> BROKER_KEY = Key.create("thePlugin.broker");
120 	private ConfigurationListenerImpl configurationListener;
121 
122 	private CrucibleEditorFactoryListener crucibleEditorFactoryListener;
123 
124 	private FileEditorListenerImpl fileEditorListener;
125 
126 	public ThePluginProjectComponent(Project project, ToolWindowManager toolWindowManager,
127 			PluginConfiguration pluginConfiguration, UIActionScheduler actionScheduler,
128 			WorkspaceConfigurationBean projectConfigurationBean, CfgManager cfgManager,
129 			@NotNull IssueListToolWindowPanel issuesToolWindowPanel,
130 			@NotNull PluginToolWindow pluginToolWindow,
131 			@NotNull BuildListModelImpl bambooModel,
132 			@NotNull final CrucibleStatusChecker crucibleStatusChecker,
133 			@NotNull final CrucibleReviewNotifier crucibleReviewNotifier,
134 			@NotNull final CrucibleReviewListModel crucibleReviewListModel,
135 			@NotNull final ProjectCfgManagerImpl projectCfgManager,
136 			@NotNull final JIRAIssueListModelBuilder jiraIssueListModelBuilder) {
137 		this.project = project;
138 		this.cfgManager = cfgManager;
139 		this.jiraIssueListModelBuilder = jiraIssueListModelBuilder;
140 		this.actionScheduler = actionScheduler;
141 		this.toolWindowManager = toolWindowManager;
142 		this.pluginConfiguration = pluginConfiguration;
143 		this.projectConfigurationBean = projectConfigurationBean;
144 		this.bambooModel = bambooModel;
145 		this.crucibleStatusChecker = crucibleStatusChecker;
146 		this.crucibleReviewNotifier = crucibleReviewNotifier;
147 		this.crucibleReviewListModel = crucibleReviewListModel;
148 		this.projectCfgManager = projectCfgManager;
149 		this.crucibleServerFacade = CrucibleServerFacadeImpl.getInstance();
150 		this.issuesToolWindowPanel = issuesToolWindowPanel;
151 		this.toolWindow = pluginToolWindow;
152 
153 		jiraIssueListModelBuilder.setProject(project);
154 		jiraIssueListModelBuilder.setProjectCfgManager(projectCfgManager);
155 		/*
156 
157 										WARNING!!!
158 		BEFORE ADDING SOME INITIALIZATION CODE TO COSTRUCTOR THINK TWICE
159                                          st
160 		...MAYBE YOU SHOULD PUT IT INTO THE initializePlugin METHOD
161 		(WHICH IS INVOKED WHEN THE ENTIRE PLUGIN ENVIRONMENT IS SET UP)?
162 
163 
164 		 */
165 		// make findBugs happy
166 		statusBarBambooIcon = null;
167 		statusBarCrucibleIcon = null;
168 		statusPluginUpdateIcon = null;
169 		created = false;
170 		StartupManager.getInstance(project).registerPostStartupActivity(new Runnable() {
171 			public void run() {
172 				LoggerImpl.getInstance().info("Start: Project initializing");
173 				initializePlugin();
174 				LoggerImpl.getInstance().info("End: Project initialized");
175 			}
176 		});
177 
178 	}
179 
180 	public void initComponent() {
181 		LoggerImpl.getInstance().info("Init ThePlugin project component.");
182 		this.fileEditorListener = new FileEditorListenerImpl(project, projectCfgManager);
183 		//ActivateJiraIssueAction.showToolbar(project);
184 	}
185 
186 	public void disposeComponent() {
187 		LoggerImpl.getInstance().info("Dispose ThePlugin project component");
188 	}
189 
190 	@NotNull
191 	public String getComponentName() {
192 		return "ThePluginProjectComponent";
193 	}
194 
195 	private void initializePlugin() {
196 		// unregister changelistmanager?
197 		// only open tool windows for each application that's registered
198 		// show something nice if there are non
199 		// swap listener for dataretrievedlistener and datachangelisteners
200 		// store bamboo between runs in UDC
201 		// clean up object model confusion
202 
203 		if (!created) {
204 
205 			toolWindow.register(toolWindowManager);
206 
207 			ChangeListManager.getInstance(project).registerCommitExecutor(
208 					new CruciblePatchSubmitExecutor(project, crucibleServerFacade, projectCfgManager));
209 
210             final MissingPasswordHandler pwdHandler = new MissingPasswordHandler(
211                     BambooServerFacadeImpl.getInstance(PluginUtil.getLogger()),
212                     projectCfgManager,
213                     project);
214 
215 			this.bambooStatusChecker = new BambooStatusChecker(
216                     actionScheduler,
217 					projectCfgManager,
218                     pluginConfiguration,
219 					new Runnable() {
220                         public void run() {
221                             MissingPasswordHandlerQueue.addHandler(pwdHandler);
222                         }
223                     },
224                     PluginUtil.getLogger());
225 
226 			// DependencyValidationManager.getHolder(project, "", )
227 
228 			issuesToolWindowPanel.refreshModels();
229 
230 			// create Atlassian tool window
231 //			toolWindow = new PluginToolWindow(toolWindowManager, project, cfgManager, bambooToolWindowPanel);
232 			Icon toolWindowIcon = IconLoader.getIcon(THE_PLUGIN_TOOL_WINDOW_ICON);
233 			toolWindow.getIdeaToolWindow().setIcon(toolWindowIcon);
234 
235 			// create tool window content
236 
237 //			toolWindow.registerPanel(PluginToolWindow.ToolWindowPanels.BAMBOO_OLD);
238 
239 			toolWindow.registerPanel(PluginToolWindow.ToolWindowPanels.BUILDS);
240 			toolWindow.registerPanel(PluginToolWindow.ToolWindowPanels.CRUCIBLE);
241 			toolWindow.registerPanel(PluginToolWindow.ToolWindowPanels.ISSUES);
242 
243 			IdeaHelper.getAppComponent().getSchedulableCheckers().add(bambooStatusChecker);
244 			// add tool window bamboo content listener to bamboo checker thread
245 			bambooStatusChecker.registerListener(new BambooStatusListener() {
246 				public void updateBuildStatuses(final Collection<BambooBuild> buildStatuses,
247 						final Collection<Exception> generalExceptions) {
248 
249 					bambooModel.update(buildStatuses, generalExceptions);
250 				}
251 
252 				public void resetState() {
253 				}
254 			});
255 
256 			// create Bamboo status bar icon
257 			statusBarBambooIcon = new BambooStatusIcon(this.project, cfgManager, toolWindow);
258 			statusBarBambooIcon.updateBambooStatus(BuildStatus.UNKNOWN, new BambooPopupInfo());
259 
260 			// add icon listener to bamboo checker thread
261 			final StatusIconBambooListener iconBambooStatusListener = new StatusIconBambooListener(statusBarBambooIcon);
262 			bambooStatusChecker.registerListener(iconBambooStatusListener);
263 
264 			// add simple bamboo listener to bamboo checker thread
265 			// this listener shows idea tooltip when buld failed
266 			final BambooStatusDisplay bambooStatusDisplay = new BuildStatusChangedToolTip(project, toolWindow);
267 			tooltipBambooStatusListener = new BambooStatusTooltipListener(bambooStatusDisplay, pluginConfiguration);
268 			bambooStatusChecker.registerListener(tooltipBambooStatusListener);
269 //			bambooStatusChecker.registerListener(buildToolWindowPanel.getBuildTree());
270 
271 			// add bamboo icon to status bar
272 			statusBarBambooIcon.showOrHideIcon();
273 
274 			// setup Crucible status checker and listeners
275 			IdeaHelper.getAppComponent().getSchedulableCheckers().add(crucibleStatusChecker);
276 			// create crucible status bar icon
277 			statusBarCrucibleIcon = new CrucibleStatusIcon(project, cfgManager, toolWindow);
278 
279 			//registerCrucibleNotifier();
280 
281 			// add crucible icon to status bar
282 			//statusBar.addCustomIndicationComponent(statusBarCrucibleIcon);
283 			statusBarCrucibleIcon.showOrHideIcon();
284 
285 			statusPluginUpdateIcon = new PluginUpdateIcon(project, pluginConfiguration, cfgManager);
286 			ConfirmPluginUpdateHandler.getInstance().setDisplay(statusPluginUpdateIcon);
287 			//statusPluginUpdateIcon.showOrHideIcon();
288 
289 			toolWindow.showHidePanels();
290 			// focus last active panel only if it exists (do not create panel)
291 			toolWindow.focusPanelIfExists(projectConfigurationBean.getActiveToolWindowTab());
292 			toolWindow.getIdeaToolWindow().getContentManager().addContentManagerListener(new ContentManagerAdapter() {
293 				@Override
294 				public void selectionChanged(final ContentManagerEvent event) {
295 					projectConfigurationBean.setActiveToolWindowTab(event.getContent().getDisplayName());
296 				}
297 			});
298 
299 			IdeaHelper.getAppComponent().rescheduleStatusCheckers(false);
300 
301 			configurationListener = new ConfigurationListenerImpl();
302 			cfgManager.addProjectConfigurationListener(CfgUtil.getProjectId(project), configurationListener);
303 			cfgManager.addProjectConfigurationListener(CfgUtil.getProjectId(project),
304 					issuesToolWindowPanel.getConfigListener());
305 
306 			created = true;
307 
308 			crucibleEditorFactoryListener = new CrucibleEditorFactoryListener(project,
309 					crucibleReviewListModel);
310 			EditorFactory.getInstance()
311 					.addEditorFactoryListener(crucibleEditorFactoryListener);
312 
313 			// added here not in plugin.xml - only for Idea 8
314 			ActionManager aManager = ActionManager.getInstance();
315 			DefaultActionGroup changesToolBar = (DefaultActionGroup) aManager.getAction("RepositoryChangesBrowserToolbar");
316 			if (changesToolBar != null) {
317 				AnAction action = aManager.getAction("ThePlugin.Crucible.ViewFisheyeChangesetItem");
318 				changesToolBar.remove(action);
319 				changesToolBar.add(action, aManager);
320 			}
321 
322 			registerCrucibleNotifier();
323 			issuesToolWindowPanel.init();
324             checkDefaultServerValues();
325 		}
326 	}
327 
328 	private void registerCrucibleNotifier() {
329 		crucibleReviewListModel.addListener(crucibleReviewNotifier);
330 		crucibleTooltip = new CrucibleNotificationTooltip(statusBarCrucibleIcon, project, toolWindow, pluginConfiguration);
331 
332 		crucibleReviewNotifier.registerListener(crucibleTooltip);
333 	}
334 
335 
336 	public void projectOpened() {
337 		// content moved to StartupManager to wait until
338 		// here we have guarantee that IDEA splash screen will not obstruct our window
339 		askForUserStatistics();
340 		fileEditorListener.projectOpened();
341 
342 	}
343 
344     private void checkDefaultServerValues() {
345         final InformationDialogWithCheckBox jiraDialog = new InformationDialogWithCheckBox(project,
346                 PluginUtil.PRODUCT_NAME,
347                 "Please set up default JIRA server in order to get all cool features of Atlassian IntelliJ Connector.");
348         if (projectCfgManager.getDefaultJiraServer() == null && !projectConfigurationBean.isDefaultJiraServerAsked()) {
349             SwingUtilities.invokeLater(new Runnable() {
350                 public void run() {
351                     jiraDialog.show();
352                     projectConfigurationBean.setDefaultJiraServerAsked(jiraDialog.isDoNotShowChecked());
353                 }
354             });
355         }
356          final InformationDialogWithCheckBox fishEyeDialog = new InformationDialogWithCheckBox(project,
357                  PluginUtil.PRODUCT_NAME,
358                 "Please set up default FishEye server in order to get all cool features of Atlassian IntelliJ Connector.");
359 
360         if (projectCfgManager.getDefaultFishEyeServer() == null
361                 && !projectConfigurationBean.isDefaultFishEyeServerAsked()) {
362             SwingUtilities.invokeLater(new Runnable() {
363                 public void run() {
364                     fishEyeDialog.show();
365                     projectConfigurationBean.setDefaultFishEyeServerAsked(fishEyeDialog.isDoNotShowChecked());
366                 }
367             });
368         }
369     }
370 
371     public FileEditorListenerImpl getFileEditorListener() {
372 		return fileEditorListener;
373 	}
374 
375 	private void askForUserStatistics() {
376 		ApplicationManager.getApplication().invokeLater(new Runnable() {
377 			public void run() {
378 				if (pluginConfiguration.getGeneralConfigurationData().getAnonymousEnhancedFeedbackEnabled() == null) {
379 					UsageStatsDialog dlg = new UsageStatsDialog();
380 					dlg.show();
381 					int answer = dlg.getExitCode();
382 					pluginConfiguration.getGeneralConfigurationData().setAnonymousEnhancedFeedbackEnabled(
383 							answer == DialogWrapper.OK_EXIT_CODE);
384 				}
385 			}
386 		}, ModalityState.defaultModalityState());
387 	}
388 
389 	private class UsageStatsDialog extends DialogWrapper {
390 		private static final String MSG_TEXT =
391 				"We would greatly appreciate it if you would allow us to collect anonymous"
392 						+ "<br>usage statistics to help us provide a better quality product. Details"
393 						+ "<br>of what will be tracked are given "
394 						+ "<a href=\""
395 						+ UsageStatisticsGenerator.USAGE_STATS_HREF
396 						+ "\">here</a>. Is this OK?";
397 
398 		protected UsageStatsDialog() {
399 			super((Project) null, false);
400 			init();
401 			setTitle(PluginUtil.getInstance().getName() + " Request");
402 			setModal(true);
403 			setOKButtonText("Yes");
404 			setCancelButtonText("No");
405 		}
406 
407 		protected JComponent createCenterPanel() {
408 			JPanel p = new JPanel(new FormLayout("3dlu, p, 3dlu, p, 3dlu", "3dlu, p, 3dlu"));
409 			CellConstraints cc = new CellConstraints();
410 
411 			JEditorPane textPane = new JEditorPane();
412 			textPane.setContentType("text/html");
413 			textPane.setEditable(false);
414 			textPane.setOpaque(false);
415 			textPane.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, true);
416 			textPane.setText("<html>" + MSG_TEXT);
417 			textPane.addHyperlinkListener(new HyperlinkListener() {
418 				public void hyperlinkUpdate(HyperlinkEvent e) {
419 					if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
420 						BrowserUtil.launchBrowser(e.getURL().toString());
421 					}
422 				}
423 			});
424 
425 			p.add(new JLabel(Messages.getQuestionIcon()), cc.xy(2, 2));
426 			p.add(textPane, cc.xy(4, 2));
427 
428 			return p;
429 		}
430 
431 
432 	}
433 
434 	public void projectClosed() {
435 		if (created) {
436 			fileEditorListener.projectClosed();
437 			// remove icon from status bar
438 			statusBarBambooIcon.hideIcon();
439 			statusBarBambooIcon = null;
440 			statusBarCrucibleIcon.hideIcon();
441 			statusBarCrucibleIcon = null;
442 			statusPluginUpdateIcon.hideIcon();
443 			statusPluginUpdateIcon = null;
444 
445 			IdeaHelper.getAppComponent().getSchedulableCheckers().remove(bambooStatusChecker);
446 			IdeaHelper.getAppComponent().getSchedulableCheckers().remove(crucibleStatusChecker);
447 			IdeaHelper.getAppComponent().rescheduleStatusCheckers(true);
448 			// unregister listeners
449 			//bambooStatusChecker.unregisterListener(iconBambooStatusListener);
450 			//bambooStatusChecker.unregisterListener(toolWindowBambooListener);
451 			bambooStatusChecker.unregisterListener(tooltipBambooStatusListener);
452 			//unregister form model
453 			//crucibleStatusChecker.unregisterListener(crucibleReviewNotifier);
454 			cfgManager.removeProjectConfigurationListener(CfgUtil.getProjectId(project), configurationListener);
455 			configurationListener = null;
456 			cfgManager.removeProjectConfigurationListener(CfgUtil.getProjectId(project),
457 					issuesToolWindowPanel.getConfigListener());
458 
459 			// remove tool window
460 			toolWindowManager.unregisterToolWindow(PluginToolWindow.TOOL_WINDOW_NAME);
461 
462 			EditorFactory.getInstance().removeEditorFactoryListener(crucibleEditorFactoryListener);
463 
464 			//remove Crucible listeners
465 			crucibleReviewNotifier.unregisterListener(crucibleTooltip);
466 			crucibleReviewListModel.removeListener(crucibleReviewNotifier);
467 
468 			ActionManager aManager = ActionManager.getInstance();
469 			DefaultActionGroup changesToolBar = (DefaultActionGroup) aManager.getAction("RepositoryChangesBrowserToolbar");
470 			if (changesToolBar != null) {
471 				AnAction action = aManager.getAction("ThePlugin.Crucible.ViewFisheyeChangesetItem");
472 				changesToolBar.remove(action);
473 			}
474 
475 			created = false;
476 		}
477 	}
478 
479 	public WorkspaceConfigurationBean getProjectConfigurationBean() {
480 		return projectConfigurationBean;
481 	}
482 
483 	public CrucibleStatusChecker getCrucibleStatusChecker() {
484 		return crucibleStatusChecker;
485 	}
486 
487 	public BambooStatusChecker getBambooStatusChecker() {
488 		return bambooStatusChecker;
489 	}
490 
491 	public JIRAIssueListModelBuilder getJiraIssueListModelBuilder() {
492 		return jiraIssueListModelBuilder;
493 	}
494 
495 	private class ConfigurationListenerImpl extends ConfigurationListenerAdapter {
496 
497 		@Override
498 		public void configurationUpdated(final ProjectConfiguration aProjectConfiguration) {
499 			// show-hide icons if necessary
500 			statusBarBambooIcon.showOrHideIcon();
501 			statusBarCrucibleIcon.showOrHideIcon();
502 			// show-hide panels if necessary
503 			toolWindow.showHidePanels();
504 		}
505 	}
506 
507 }