package net.sourceforge.cruisecontrol.distributed; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Logger; import org.apache.log4j.spi.LoggingEvent; import java.awt.Image; import java.awt.event.ActionEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.BorderLayout; import java.awt.GridLayout; import java.awt.Dimension; import java.awt.Window; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JButton; import javax.swing.JTextArea; import javax.swing.JScrollPane; import javax.swing.text.BadLocationException; import javax.swing.SwingUtilities; import javax.swing.JOptionPane; import javax.swing.Action; import javax.swing.AbstractAction; import java.rmi.RemoteException; import java.util.prefs.Preferences; import net.sourceforge.cruisecontrol.distributed.core.MulticastDiscovery; import net.sourceforge.cruisecontrol.distributed.core.CCDistVersion; import net.sourceforge.cruisecontrol.distributed.core.PreferencesHelper; /** * @author Dan Rollo * Date: Aug 25, 2005 * Time: 3:38:53 PM */ // @todo Use JDesktop stuff for tray icon?? final class BuildAgentUI extends JFrame implements BuildAgent.AgentStatusListener, BuildAgent.LUSCountListener, PreferencesHelper.UIPreferences { private static final Logger LOG = Logger.getLogger(BuildAgentUI.class); private static final int CONSOLE_LINE_BUFFER_SIZE = 1000; private final BuildAgent buildAgent; private final JTextArea txaAgentInfo; private final Action atnStop; private final Action atnEditEntries; private final String origTitle; BuildAgentUI(final BuildAgent parentbuildAgent) { super("CruiseControl - Build Agent " + CCDistVersion.getVersion()); origTitle = getTitle(); buildAgent = parentbuildAgent; buildAgent.addLUSCountListener(this); buildAgent.addAgentStatusListener(this); atnStop = new AbstractAction("Stop") { public void actionPerformed(final ActionEvent e) { doExit(); } }; addWindowListener(new WindowAdapter() { public void windowClosing(final WindowEvent evt) { doExit(); } }); final JTextArea txaConsole = new JTextArea(); txaConsole.setFont(new java.awt.Font("Courier New", 0, 12)); final JScrollPane scrConsole = new JScrollPane(); scrConsole.setViewportView(txaConsole); scrConsole.setPreferredSize(new Dimension(550, 300)); txaConsole.setEditable(false); // need to register with BuildAgent Logger (not UI Logger). BuildAgent.LOG.addAppender(new Log4JJTextAreaAppender(txaConsole)); final JPanel pnlN = new JPanel(new BorderLayout()); txaAgentInfo = new JTextArea("Registering with Lookup Services..."); txaAgentInfo.setEditable(false); pnlN.add(txaAgentInfo, BorderLayout.CENTER); final JButton btnStop = new JButton(atnStop); btnStop.setToolTipText("Terminates this Build Agent. (If agent is busy, the build may not stop.)"); btnStop.setPreferredSize(new Dimension(62, -1)); final JPanel pnlButtons = new JPanel(new GridLayout(0, 1)); pnlButtons.add(btnStop); atnEditEntries = new AbstractAction("Entries") { public void actionPerformed(ActionEvent e) { doEditEntries(); } }; final JButton btnEntries = new JButton(atnEditEntries); btnEntries.setToolTipText("Edit Entry Overrides. System provided entries are not editable."); pnlButtons.add(btnEntries); pnlN.add(pnlButtons, BorderLayout.EAST); getContentPane().setLayout(new BorderLayout()); getContentPane().add(pnlN, BorderLayout.NORTH); getContentPane().add(scrConsole, BorderLayout.CENTER); final Image imgIcon = PreferencesHelper.getCCImageIcon(); if (imgIcon != null) { setIconImage(imgIcon); } pack(); // apply screen info from last run PreferencesHelper.applyWindowInfo(this); setVisible(true); } private void doEditEntries() { try { final BuildAgentService agentService = buildAgent.getService(); final BuildAgentEntryOverrideUI ui = new BuildAgentEntryOverrideUI(this, agentService, agentService.getMachineName() + ": " + buildAgent.getServiceID()); ui.addWindowListener(new WindowAdapter() { public void windowClosing(final WindowEvent evt) { atnEditEntries.setEnabled(true); } }); atnEditEntries.setEnabled(false); } catch (RemoteException e1) { final String msg = "An error occurred while editing entry overrides: "; LOG.error(msg, e1); JOptionPane.showMessageDialog(BuildAgentUI.this, msg + e1.getMessage(), "Error Editing Entry Overrides", JOptionPane.ERROR_MESSAGE); } } public void dispose() { // ensure we do cleanup even when closing due to webstart resart // save screen info PreferencesHelper.saveWindowInfo(this); super.dispose(); } private void doExit() { atnStop.setEnabled(false); final BuildAgentUI theThis = this; new Thread("Build Agent doExit Thread") { public void run() { BuildAgent.kill(); // on some JVM's the kill call above doesn't return, so sys exit is done by main() LOG.info("BuildAgent.kill() completed"); buildAgent.removeAgentStatusListener(theThis); LOG.info("AgentStatusListener removed"); buildAgent.removeLUSCountListener(theThis); LOG.info("LUSCountListener removed"); System.exit(0); } } .start(); } public void statusChanged(BuildAgentService buildAgentService) { updateAgentInfoUI(buildAgentService); } void updateAgentInfoUI(final BuildAgentService buildAgentService) { // collect data to update on separate thread // it's also good to wait a bit for the agent info to be updated... new Thread("AgentUI updateAgentInfoUI-data") { public void run() { doUpdateAgentInfoUI(buildAgentService); } } .start(); } private void doUpdateAgentInfoUI(final BuildAgentService buildAgentService) { String agentInfo = ""; if (buildAgentService != null) { try { agentInfo = buildAgentService.asString(); } catch (RemoteException e) { agentInfo = e.getMessage(); } } final String agentText = "Build Agent: " + buildAgent.getServiceID() + "\n" + agentInfo + MulticastDiscovery.toStringEntries(buildAgent.getEntries()); // only update UI on event dispatch thread, //"AgentUI updateAgentInfoUI Thread" SwingUtilities.invokeLater(new Runnable() { public void run() { txaAgentInfo.setText(agentText); } }); } public void lusCountChanged(final int newLUSCount) { //"Agent lusCountChanged Thread" SwingUtilities.invokeLater(new Runnable() { public void run() { setTitle(origTitle + ", LUS's: " + newLUSCount); } }); } public Preferences getPrefsBase() { return buildAgent.getPrefsRoot(); } public Window getWindow() { return this; } /** * Log4J appender to duplicate log output to a JTextArea */ public class Log4JJTextAreaAppender extends AppenderSkeleton { private final JTextArea txaL4JConsole; public Log4JJTextAreaAppender(final JTextArea txaConsole) { this.txaL4JConsole = txaConsole; } protected void append(final LoggingEvent event) { final String msg = event.getRenderedMessage(); //"Agent Log4JJTextAreaAppender Thread" SwingUtilities.invokeLater(new Runnable() { public void run() { txaL4JConsole.append(msg + "\n"); if (txaL4JConsole.getLineCount() > CONSOLE_LINE_BUFFER_SIZE) { // remove old lines try { txaL4JConsole.replaceRange("", 0, txaL4JConsole.getLineEndOffset( txaL4JConsole.getLineCount() - CONSOLE_LINE_BUFFER_SIZE )); } catch (BadLocationException e) { //ignore } } // Make sure the last line is always visible txaL4JConsole.setCaretPosition(txaL4JConsole.getDocument().getLength()); } }); } public boolean requiresLayout() { return false; } public void close() { } } }