001: package net.sourceforge.cruisecontrol.distributed.util;
002:
003: import org.apache.log4j.Logger;
004: import net.jini.core.lookup.ServiceItem;
005: import net.jini.core.lookup.ServiceID;
006: import net.sourceforge.cruisecontrol.distributed.BuildAgentService;
007: import net.sourceforge.cruisecontrol.distributed.BuildAgentEntryOverrideUI;
008: import net.sourceforge.cruisecontrol.distributed.core.MulticastDiscovery;
009: import net.sourceforge.cruisecontrol.distributed.core.CCDistVersion;
010: import net.sourceforge.cruisecontrol.distributed.core.PreferencesHelper;
011:
012: import javax.swing.JFrame;
013: import javax.swing.JPanel;
014: import javax.swing.JButton;
015: import javax.swing.JTextArea;
016: import javax.swing.JScrollPane;
017: import javax.swing.JComboBox;
018: import javax.swing.JCheckBox;
019: import javax.swing.ComboBoxModel;
020: import javax.swing.DefaultComboBoxModel;
021: import javax.swing.text.BadLocationException;
022: import javax.swing.SwingUtilities;
023: import javax.swing.Action;
024: import javax.swing.AbstractAction;
025: import javax.swing.JOptionPane;
026: import java.rmi.RemoteException;
027: import java.awt.event.ActionListener;
028: import java.awt.event.ActionEvent;
029: import java.awt.event.WindowAdapter;
030: import java.awt.event.WindowEvent;
031: import java.awt.BorderLayout;
032: import java.awt.Dimension;
033: import java.awt.Font;
034: import java.awt.GridLayout;
035: import java.awt.Window;
036: import java.util.List;
037: import java.util.ArrayList;
038: import java.util.Arrays;
039: import java.util.prefs.Preferences;
040:
041: /**
042: * @author Dan Rollo
043: * Date: Aug 1, 2005
044: * Time: 4:00:38 PM
045: */
046: public final class BuildAgentUtility {
047: private static final Logger LOG = Logger
048: .getLogger(BuildAgentUtility.class);
049:
050: // helps make UI class testable in a headless environment
051: static interface UISetInfo {
052: void setInfo(final String infoText);
053: }
054:
055: // @todo make BuidAgentService implement/extend jini ServiceUI?
056: static class UI extends JFrame implements
057: PreferencesHelper.UIPreferences, UISetInfo {
058:
059: private static final int CONSOLE_LINE_BUFFER_SIZE = 1000;
060:
061: private final String origTitle;
062: private final BuildAgentUtility buildAgentUtility;
063: private final JButton btnRefresh = new JButton("Refresh");
064: private final JComboBox cmbAgents = new JComboBox();
065: private final Action atnInvoke;
066: private final Action atnEditEntries;
067: private static final String METH_RESTART = "restart";
068: private static final String METH_KILL = "kill";
069: private final JComboBox cmbRestartOrKill = new JComboBox(
070: new String[] { METH_RESTART, METH_KILL });
071: private final JCheckBox chkAfterBuildFinished = new JCheckBox(
072: "Wait for build to finish.", true);
073: private final JButton btnInvokeOnAll = new JButton(
074: "Invoke on All");
075: private final JPanel pnlEdit = new JPanel(new BorderLayout());
076: private final JButton btnClose = new JButton("Close");
077: private final JTextArea txaConsole = new JTextArea();
078: private final JScrollPane scrConsole = new JScrollPane();
079:
080: private static final Preferences PREFS_BASE = Preferences
081: .userNodeForPackage(UI.class);
082:
083: static Preferences getPrefsRoot() {
084: return PREFS_BASE;
085: }
086:
087: /**
088: * No-arg constructor for use in unit tests, to be overridden by MockUI.
089: */
090: UI() {
091: origTitle = null;
092: buildAgentUtility = null;
093: atnInvoke = null;
094: atnEditEntries = null;
095: }
096:
097: private UI(final BuildAgentUtility buildAgentUtil) {
098: super ("CruiseControl - Agent Utility "
099: + CCDistVersion.getVersion());
100:
101: origTitle = getTitle();
102:
103: buildAgentUtility = buildAgentUtil;
104:
105: btnClose.addActionListener(new ActionListener() {
106: public void actionPerformed(final ActionEvent e) {
107: exitForm();
108: }
109: });
110: addWindowListener(new WindowAdapter() {
111: public void windowClosing(final WindowEvent evt) {
112: exitForm();
113: }
114: });
115:
116: btnRefresh.addActionListener(new ActionListener() {
117: public void actionPerformed(final ActionEvent e) {
118: refreshAgentList();
119: }
120: });
121:
122: cmbAgents.addActionListener(new ActionListener() {
123: public void actionPerformed(final ActionEvent e) {
124: atnInvoke.setEnabled(true);
125: atnEditEntries.setEnabled(true);
126: }
127: });
128:
129: atnInvoke = new AbstractAction("Invoke") {
130: public void actionPerformed(final ActionEvent e) {
131: try {
132: invokeOnAgent(((ComboItemWrapper) cmbAgents
133: .getSelectedItem()).getAgent());
134: } catch (RemoteException e1) {
135: checkRestartRequiresWebStart(e1);
136: LOG.info(e1.getMessage());
137: appendInfo(e1.getMessage());
138: throw new RuntimeException(e1);
139: }
140: }
141: };
142: atnInvoke.setEnabled(false);
143:
144: atnEditEntries = new AbstractAction("Entries") {
145: public void actionPerformed(final ActionEvent e) {
146: try {
147: final ComboItemWrapper agentWrapper = ((ComboItemWrapper) cmbAgents
148: .getSelectedItem());
149: final BuildAgentService agentService = agentWrapper
150: .getAgent();
151:
152: new BuildAgentEntryOverrideUI(
153: BuildAgentUtility.UI.this ,
154: agentService, agentService
155: .getMachineName()
156: + ": "
157: + agentWrapper.getServiceID());
158:
159: } catch (RemoteException e1) {
160: final String msg = "An error occurred while editing entry overrides: ";
161: LOG.error(msg, e1);
162: appendInfo(msg + e1.getMessage());
163: JOptionPane.showMessageDialog(
164: BuildAgentUtility.UI.this , msg + "\n\n"
165: + e1.getMessage(),
166: "Error Editing Entry Overrides",
167: JOptionPane.ERROR_MESSAGE);
168: throw new RuntimeException(e1);
169: }
170: }
171: };
172: atnEditEntries.setEnabled(false);
173:
174: btnInvokeOnAll.addActionListener(new ActionListener() {
175: public void actionPerformed(final ActionEvent e) {
176: for (int i = 0; i < cmbAgents.getItemCount(); i++) {
177: try {
178: invokeOnAgent(((ComboItemWrapper) cmbAgents
179: .getItemAt(i)).getAgent());
180: } catch (RemoteException e1) {
181: checkRestartRequiresWebStart(e1);
182: LOG.info(e1.getMessage());
183: appendInfo(e1.getMessage());
184: //throw new RuntimeException(e1); // allow remaining items to be invoked
185: }
186: }
187: }
188: });
189:
190: txaConsole.setFont(new Font("Courier New", 0, 12));
191:
192: scrConsole.setViewportView(txaConsole);
193: scrConsole.setPreferredSize(new Dimension(626, 300));
194:
195: getContentPane().setLayout(new BorderLayout());
196: final JPanel pnlNN = new JPanel(new BorderLayout());
197: pnlNN.add(btnRefresh, BorderLayout.WEST);
198:
199: pnlNN.add(cmbAgents, BorderLayout.CENTER);
200:
201: final JPanel pnlButtonsTop = new JPanel(
202: new GridLayout(1, 0));
203: final JButton btnInvoke = new JButton(atnInvoke);
204: btnInvoke.setPreferredSize(new Dimension(76, -1));
205: pnlButtonsTop.add(btnInvoke);
206: pnlButtonsTop.add(new JButton(atnEditEntries));
207: pnlNN.add(pnlButtonsTop, BorderLayout.EAST);
208:
209: final JPanel pnlNS = new JPanel(new BorderLayout());
210: pnlNS.add(btnClose, BorderLayout.EAST);
211:
212: pnlEdit.add(cmbRestartOrKill, BorderLayout.WEST);
213: pnlEdit.add(chkAfterBuildFinished, BorderLayout.CENTER);
214: pnlEdit.add(btnInvokeOnAll, BorderLayout.EAST);
215:
216: final JPanel northPanel = new JPanel(new BorderLayout());
217: northPanel.add(pnlNN, BorderLayout.NORTH);
218: northPanel.add(pnlEdit, BorderLayout.CENTER);
219: northPanel.add(pnlNS, BorderLayout.SOUTH);
220: getContentPane().add(northPanel, BorderLayout.NORTH);
221: getContentPane().add(scrConsole, BorderLayout.CENTER);
222: pack();
223:
224: // set screen info from last run
225: PreferencesHelper.applyWindowInfo(this );
226:
227: setVisible(true);
228: }
229:
230: private void checkRestartRequiresWebStart(RemoteException e) {
231: if (e.getCause() != null
232: && e.getCause() instanceof ClassNotFoundException
233: && "javax.jnlp.UnavailableServiceException"
234: .equals(e.getCause().getMessage())) {
235:
236: final String msg = "\nNOTE: Restart feature is only available on Agents launched via WebStart.\n"
237: + e.getCause().getMessage();
238: LOG.info(msg);
239: appendInfo(msg);
240: }
241: }
242:
243: private void invokeOnAgent(final BuildAgentService agent)
244: throws RemoteException {
245: if (METH_RESTART.equals(cmbRestartOrKill.getSelectedItem())) {
246: agent.restart(chkAfterBuildFinished.isSelected());
247: } else {
248: agent.kill(chkAfterBuildFinished.isSelected());
249: }
250: }
251:
252: // begin implementation of UIPreferences
253: public Preferences getPrefsBase() {
254: return UI.getPrefsRoot();
255: }
256:
257: public Window getWindow() {
258: return this ;
259: }
260:
261: // end implementation of UIPreferences
262:
263: private static final class ComboItemWrapper {
264: private static ComboItemWrapper[] wrapArray(
265: final ServiceItem[] serviceItems) {
266: final ComboItemWrapper[] result = new ComboItemWrapper[serviceItems.length];
267: for (int i = 0; i < serviceItems.length; i++) {
268: result[i] = new ComboItemWrapper(serviceItems[i]);
269: }
270: return result;
271: }
272:
273: private final ServiceItem serviceItem;
274:
275: private ComboItemWrapper(final ServiceItem serviceItemToWrap) {
276: this .serviceItem = serviceItemToWrap;
277: }
278:
279: public BuildAgentService getAgent() {
280: return (BuildAgentService) serviceItem.service;
281: }
282:
283: public ServiceID getServiceID() {
284: return serviceItem.serviceID;
285: }
286:
287: private String errToString;
288:
289: public String toString() {
290: // don't make remote calls if agent has errored out already, otherwise may appear to hang ui
291: if (errToString != null) {
292: return errToString;
293: }
294:
295: try {
296: return getAgent().getMachineName() + ": "
297: + serviceItem.serviceID;
298: } catch (RemoteException e) {
299: errToString = "Remote Error: " + e.getMessage();
300: } catch (Exception e) {
301: errToString = "Error: " + e.getMessage();
302: }
303: return errToString;
304: }
305: }
306:
307: private void refreshAgentList() {
308: SwingUtilities.invokeLater(new Thread(
309: "BuildAgentUtility btn.disable Thread") {
310: public void run() {
311: btnRefresh.setEnabled(false);
312: atnInvoke.setEnabled(false);
313: atnEditEntries.setEnabled(false);
314: btnInvokeOnAll.setEnabled(false);
315: cmbAgents.setEnabled(false);
316: }
317: });
318: new Thread("BuildAgentUtility refreshAgentList Thread") {
319: public void run() {
320: doRefreshAgentList();
321: }
322: }.start();
323: }
324:
325: private void doRefreshAgentList() {
326: try {
327: final List tmpList = new ArrayList();
328: final String agentInfoAll = buildAgentUtility
329: .getAgentInfoAll(tmpList);
330: final ServiceItem[] serviceItems = (ServiceItem[]) tmpList
331: .toArray(new ServiceItem[tmpList.size()]);
332: final ComboBoxModel comboBoxModel = new DefaultComboBoxModel(
333: ComboItemWrapper.wrapArray(serviceItems));
334: SwingUtilities.invokeLater(new Thread(
335: "BuildAgentUtility setcomboBoxModel Thread") {
336: public void run() {
337: UI.this .setTitle(origTitle + ", LUS's: "
338: + buildAgentUtility.lastLUSCount);
339: cmbAgents.setModel(comboBoxModel);
340: }
341: });
342: setInfo(agentInfoAll);
343: } finally {
344: SwingUtilities.invokeLater(new Thread(
345: "BuildAgentUtility btn.enable Thread") {
346: public void run() {
347: btnRefresh.setEnabled(true);
348: btnInvokeOnAll.setEnabled(true);
349: cmbAgents.setEnabled(true);
350: }
351: });
352: }
353: }
354:
355: private void exitForm() {
356: // save screen info
357: PreferencesHelper.saveWindowInfo(this );
358:
359: System.exit(0);
360: }
361:
362: // Only public to allow testing of UI in headless environs
363: public void setInfo(final String infoText) {
364: LOG.debug(infoText);
365: SwingUtilities.invokeLater(new Thread(
366: "BuildAgentUtility txaConsole.setInfo Thread") {
367: public void run() {
368: txaConsole.setText(infoText);
369: }
370: });
371: }
372:
373: private void appendInfo(final String infoText) {
374: SwingUtilities.invokeLater(new Thread(
375: "BuildAgentUtility txaConsole.appendInfo Thread") {
376: public void run() {
377: txaConsole.append(infoText + "\n");
378: if (txaConsole.getLineCount() > CONSOLE_LINE_BUFFER_SIZE) {
379: // remove old lines
380: try {
381: txaConsole
382: .replaceRange(
383: "",
384: 0,
385: txaConsole
386: .getLineEndOffset(txaConsole
387: .getLineCount()
388: - CONSOLE_LINE_BUFFER_SIZE));
389: } catch (BadLocationException e) {
390: //ignore
391: }
392: }
393: // Make sure the last line is always visible
394: txaConsole.setCaretPosition(txaConsole
395: .getDocument().getLength());
396: }
397: });
398:
399: }
400: }
401:
402: private final UISetInfo ui;
403:
404: private int lastLUSCount;
405:
406: private boolean isFailFast;
407: private boolean isInited;
408:
409: private BuildAgentUtility() {
410: this (null);
411: }
412:
413: BuildAgentUtility(final UISetInfo mockUI) {
414:
415: CCDistVersion.printCCDistVersion();
416:
417: if (mockUI == null) {
418: ui = new UI(this );
419: ((UI) ui).btnRefresh.doClick();
420: } else {
421: ui = mockUI;
422: isFailFast = true;
423: }
424: }
425:
426: String getAgentInfoAll(final List lstServiceItems) {
427:
428: final StringBuffer result = new StringBuffer();
429: try {
430:
431: if (!isInited && !isFailFast) {
432: try {
433: MulticastDiscovery.begin();
434: final String msgWaitLUS = "Waiting 5 seconds for registrars to report in...";
435: ui.setInfo(msgWaitLUS);
436: LOG.info(msgWaitLUS);
437:
438: Thread.sleep(5000);
439: isInited = true;
440: } catch (InterruptedException e1) {
441: LOG.warn("Sleep interrupted", e1);
442: }
443: }
444:
445: final String waitMessage = "Waiting for Build Agents to report in...";
446: ui.setInfo(waitMessage);
447: LOG.info(waitMessage);
448: final ServiceItem[] serviceItems = MulticastDiscovery
449: .findBuildAgentServices(
450: null,
451: (isFailFast ? 0
452: : MulticastDiscovery.DEFAULT_FIND_WAIT_DUR_MILLIS));
453:
454: // update LUS count
455: lastLUSCount = MulticastDiscovery.getLUSCount();
456:
457: // clear and rebuild list
458: lstServiceItems.clear();
459: lstServiceItems.addAll(Arrays.asList(serviceItems));
460:
461: LOG.info("Agents found: " + serviceItems.length);
462: result.append("Found: ").append(serviceItems.length)
463: .append(" agent").append(
464: serviceItems.length != 1 ? "s" : "")
465: .append(".\n");
466:
467: ServiceItem serviceItem;
468: BuildAgentService agent;
469: String agentInfo;
470: for (int x = 0; x < serviceItems.length; x++) {
471: serviceItem = serviceItems[x];
472: agent = (BuildAgentService) serviceItem.service;
473: agentInfo = "Build Agent: "
474: + serviceItem.serviceID
475: + "\n"
476: + agent.asString()
477: + MulticastDiscovery
478: .toStringEntries(serviceItem.attributeSets)
479: + "\n";
480: LOG.debug(agentInfo);
481: result.append(agentInfo);
482: }
483: } catch (RemoteException e) {
484: final String message = "Search failed due to an unexpected error";
485: LOG.error(message, e);
486: throw new RuntimeException(message, e);
487: }
488:
489: return result.toString();
490: }
491:
492: public static void main(final String[] args) {
493: new BuildAgentUtility();
494: }
495: }
|