View Javadoc
1   package com.github.sbugat.rundeckmonitor;
2   
3   import java.awt.Desktop;
4   import java.awt.Image;
5   import java.awt.SystemTray;
6   import java.awt.Toolkit;
7   import java.awt.TrayIcon;
8   import java.awt.event.ActionEvent;
9   import java.awt.event.ActionListener;
10  import java.awt.event.WindowEvent;
11  import java.awt.event.WindowFocusListener;
12  import java.io.IOException;
13  import java.io.PrintWriter;
14  import java.io.StringWriter;
15  import java.net.URI;
16  import java.net.URISyntaxException;
17  import java.util.HashSet;
18  import java.util.List;
19  import java.util.Set;
20  
21  import javax.swing.Icon;
22  import javax.swing.ImageIcon;
23  import javax.swing.JDialog;
24  import javax.swing.JOptionPane;
25  import javax.swing.UIManager;
26  import javax.swing.UnsupportedLookAndFeelException;
27  
28  import org.slf4j.ext.XLogger;
29  import org.slf4j.ext.XLoggerFactory;
30  
31  import com.github.sbugat.rundeckmonitor.configuration.RundeckMonitorConfiguration;
32  import com.github.sbugat.rundeckmonitor.tools.SystemTools;
33  import com.github.sbugat.rundeckmonitor.wizard.JobTabRedirection;
34  import com.github.sbugat.rundeckmonitor.wizard.RundeckMonitorConfigurationWizard;
35  
36  /**
37   * Tray icon common class with no-AWT/Swing components.
38   * 
39   * @author Sylvain Bugat
40   * 
41   */
42  public abstract class RundeckMonitorTrayIcon {
43  
44  	/** SLF4J XLogger. */
45  	private static final XLogger LOG = XLoggerFactory.getXLogger(RundeckMonitor.class);
46  
47  	/** URL to access job execution details. */
48  	static final String RUNDECK_JOB_EXECUTION_URL = "/execution/"; //$NON-NLS-1$
49  
50  	/** GitHub Project URL. */
51  	static final String RUNDECK_MONITOR_PROJECT_URL = "https://sylvain-bugat.github.com/RundeckMonitor"; //$NON-NLS-1$
52  
53  	/** Marker on the job when it is too long. */
54  	static final String LONG_EXECUTION_MARKER = " - LONG EXECUTION"; //$NON-NLS-1$
55  
56  	/** Alert message when a new failed job is detected. */
57  	static final String NEW_FAILED_JOB_ALERT = "New failed job"; //$NON-NLS-1$
58  
59  	/** Alert message when a new long execution is detected. */
60  	static final String NEW_LONG_EXECUTION_ALERT = "New long execution"; //$NON-NLS-1$
61  
62  	/** OK image. */
63  	static final Image IMAGE_OK = Toolkit.getDefaultToolkit().getImage(RundeckMonitorTrayIcon.class.getClassLoader().getResource("OK.png")); //$NON-NLS-1$
64  	/** WARNING image when a job seems to be blocked. */
65  	private static final Image IMAGE_LATE = Toolkit.getDefaultToolkit().getImage(RundeckMonitorTrayIcon.class.getClassLoader().getResource("LATE.png")); //$NON-NLS-1$
66  	static final Icon ICON_LATE_SMALL = new ImageIcon(Toolkit.getDefaultToolkit().getImage(RundeckMonitorTrayIcon.class.getClassLoader().getResource("LATE_SMALL.png"))); //$NON-NLS-1$
67  	/** KO image when a job has failed. */
68  	private static final Image IMAGE_KO = Toolkit.getDefaultToolkit().getImage(RundeckMonitorTrayIcon.class.getClassLoader().getResource("KO.png")); //$NON-NLS-1$
69  	static final Icon ICON_KO_SMALL = new ImageIcon(Toolkit.getDefaultToolkit().getImage(RundeckMonitorTrayIcon.class.getClassLoader().getResource("KO_SMALL.png"))); //$NON-NLS-1$
70  	/** KO image when a job has failed and a job seems to be blocked. */
71  	private static final Image IMAGE_KO_LATE = Toolkit.getDefaultToolkit().getImage(RundeckMonitorTrayIcon.class.getClassLoader().getResource("KO_LATE.png")); //$NON-NLS-1$
72  	/** Disconnected from rundeck image. */
73  	private static final Image IMAGE_DISCONNECTED = Toolkit.getDefaultToolkit().getImage(RundeckMonitorTrayIcon.class.getClassLoader().getResource("DISCONNECTED.png")); //$NON-NLS-1$
74  
75  	/** System tray. */
76  	final SystemTray tray;
77  
78  	/** Task bar tray icon. */
79  	TrayIcon trayIcon;
80  
81  	/** Desktop to get the default browser. */
82  	final Desktop desktop;
83  
84  	/** Menu failed item listener. */
85  	ActionListener menuListener;
86  
87  	/** Edit configuration listener. */
88  	final ActionListener configurationListener;
89  
90  	/** About menu listener. */
91  	final ActionListener aboutListener;
92  
93  	/** Exit menu listener. */
94  	final ActionListener exitListener;
95  
96  	/** Dialog to auto-hade the popup menu. */
97  	JDialog hiddenDialog;
98  
99  	/** Date format to use for printing the Job start date. */
100 	final RundeckMonitorConfiguration rundeckMonitorConfiguration;
101 
102 	/** Current state of the trayIcon. */
103 	final RundeckMonitorState rundeckMonitorState;
104 
105 	/** Already known late/long process. */
106 	final Set<Long> newLateProcess = new HashSet<>();
107 
108 	/** Already known failed process. */
109 	final Set<Long> newFailedProcess = new HashSet<>();
110 
111 	/**
112 	 * Initialize the tray icon for the rundeckMonitor if the OS is compatible with it.
113 	 * 
114 	 * @param rundeckMonitorConfigurationArg loaded configuration
115 	 * @param rundeckMonitorStateArg state of the rundeck monitor
116 	 */
117 	public RundeckMonitorTrayIcon(final RundeckMonitorConfiguration rundeckMonitorConfigurationArg, final RundeckMonitorState rundeckMonitorStateArg) {
118 
119 		LOG.entry(rundeckMonitorConfigurationArg, rundeckMonitorStateArg);
120 
121 		rundeckMonitorConfiguration = rundeckMonitorConfigurationArg;
122 		rundeckMonitorState = rundeckMonitorStateArg;
123 
124 		if (SystemTray.isSupported()) {
125 
126 			// Try to use the system Look&Feel
127 			try {
128 				UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
129 			}
130 			catch (final ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
131 
132 				// If System Look&Feel is not supported, stay with the default one
133 				LOG.warn("Unsupported System Look&Feel", e); //$NON-NLS-1$
134 			}
135 
136 			// Get the system default browser to open execution details
137 			desktop = Desktop.getDesktop();
138 
139 			// Edit configuration listener
140 			configurationListener = new ActionListener() {
141 
142 				@Override
143 				@SuppressWarnings("synthetic-access")
144 				public void actionPerformed(final ActionEvent e) {
145 					new RundeckMonitorConfigurationWizard(new RundeckMonitorConfiguration(rundeckMonitorConfiguration), false);
146 				}
147 			};
148 
149 			// Rundeck monitor about
150 			aboutListener = new ActionListener() {
151 
152 				@Override
153 				@SuppressWarnings("synthetic-access")
154 				public void actionPerformed(final ActionEvent e) {
155 
156 					try {
157 						final URI executionURI = new URI(RUNDECK_MONITOR_PROJECT_URL);
158 						desktop.browse(executionURI);
159 					}
160 					catch (final URISyntaxException | IOException exception) {
161 
162 						final StringWriter stringWriter = new StringWriter();
163 						exception.printStackTrace(new PrintWriter(stringWriter));
164 						JOptionPane.showMessageDialog(null, exception.getMessage() + System.lineSeparator() + stringWriter.toString(), "RundeckMonitor redirection error", JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
165 					}
166 				}
167 			};
168 
169 			// Get the system tray
170 			tray = SystemTray.getSystemTray();
171 
172 			// Rundeck monitor exit
173 			exitListener = new ActionListener() {
174 
175 				@Override
176 				@SuppressWarnings("synthetic-access")
177 				public void actionPerformed(final ActionEvent e) {
178 					tray.remove(trayIcon);
179 					SystemTools.exit(SystemTools.EXIT_CODE_OK);
180 				}
181 			};
182 
183 			hiddenDialog = new JDialog();
184 			hiddenDialog.setSize(10, 10);
185 
186 			hiddenDialog.addWindowFocusListener(new WindowFocusListener() {
187 
188 				@Override
189 				public void windowLostFocus(final WindowEvent e) {
190 					hiddenDialog.setVisible(false);
191 				}
192 
193 				@Override
194 				public void windowGainedFocus(final WindowEvent e) {
195 					// Nothing to do
196 				}
197 			});
198 		}
199 		else {
200 			// if the System is not compatible with SystemTray
201 			tray = null;
202 			desktop = null;
203 			configurationListener = null;
204 			aboutListener = null;
205 			exitListener = null;
206 
207 			JOptionPane.showMessageDialog(null, "SystemTray cannot be initialized", "RundeckMonitor initialization error", JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$ //$NON-NLS-2$
208 
209 			SystemTools.exit(SystemTools.EXIT_CODE_TRAY_ICON_UNSUPPORTED);
210 		}
211 	}
212 
213 	/**
214 	 * Update the list of failed/late jobs.
215 	 * 
216 	 * @param listJobExecutionInfo list of failed and late jobs informations
217 	 */
218 	public abstract void updateExecutionIdsList(final List<JobExecutionInfo> listJobExecutionInfo);
219 
220 	/**
221 	 * Update the image of the tray icon.
222 	 */
223 	public final void updateTrayIcon() {
224 
225 		LOG.entry();
226 
227 		if (rundeckMonitorState.isDisconnected()) {
228 			trayIcon.setImage(IMAGE_DISCONNECTED);
229 		}
230 		else if (rundeckMonitorState.isFailedJobs()) {
231 
232 			if (rundeckMonitorState.isLateJobs()) {
233 				trayIcon.setImage(IMAGE_KO_LATE);
234 			}
235 			else {
236 				trayIcon.setImage(IMAGE_KO);
237 			}
238 		}
239 		else if (rundeckMonitorState.isLateJobs()) {
240 			trayIcon.setImage(IMAGE_LATE);
241 		}
242 		else {
243 			trayIcon.setImage(IMAGE_OK);
244 		}
245 
246 		LOG.exit();
247 	}
248 
249 	/**
250 	 * Called when configuration is reloaded, clear all known process and reinitialize the tooltip.
251 	 */
252 	public void reloadConfiguration() {
253 
254 		LOG.entry();
255 
256 		newLateProcess.clear();
257 		newFailedProcess.clear();
258 
259 		trayIcon.setToolTip(rundeckMonitorConfiguration.getRundeckMonitorName());
260 
261 		LOG.exit();
262 	}
263 
264 	/**
265 	 * remove the RundeckMonitor icon from the system tray.
266 	 */
267 	public final void disposeTrayIcon() {
268 
269 		LOG.entry();
270 		tray.remove(trayIcon);
271 		LOG.exit();
272 	}
273 
274 	/**
275 	 * Open a browser page using the default browser to a job execution.
276 	 * 
277 	 * @param jobExecutionInfo the job exeuction to open
278 	 */
279 	final void openBrowser(final JobExecutionInfo jobExecutionInfo) {
280 
281 		LOG.entry();
282 		final JobTabRedirection jobTabRedirection;
283 
284 		if (jobExecutionInfo.isLongExecution()) {
285 			jobTabRedirection = JobTabRedirection.SUMMARY;
286 		}
287 		else {
288 			jobTabRedirection = JobTabRedirection.valueOf(rundeckMonitorConfiguration.getJobTabRedirection());
289 		}
290 
291 		try {
292 			final String uRI = rundeckMonitorConfiguration.getRundeckUrl() + RUNDECK_JOB_EXECUTION_URL + jobTabRedirection.getAccessUrlPrefix() + '/' + jobExecutionInfo.getExecutionId() + jobTabRedirection.getAccessUrlSuffix();
293 			LOG.info("Open execution with URL: {}", uRI); //$NON-NLS-1$
294 			final URI executionURI = new URI(uRI);
295 			desktop.browse(executionURI);
296 		}
297 		catch (final URISyntaxException | IOException exception) {
298 
299 			final StringWriter stringWriter = new StringWriter();
300 			exception.printStackTrace(new PrintWriter(stringWriter));
301 			JOptionPane.showMessageDialog(null, exception.getMessage() + System.lineSeparator() + stringWriter.toString(), "RundeckMonitor redirection error", JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
302 		}
303 
304 		LOG.exit();
305 	}
306 }