001: /*
002: * <copyright>
003: *
004: * Copyright 2000-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.tools.csmart.ui.console;
028:
029: import org.cougaar.core.agent.AgentManager;
030: import org.cougaar.core.component.ComponentDescription;
031: import org.cougaar.core.plugin.PluginManager;
032: import org.cougaar.tools.csmart.core.cdata.AgentComponentData;
033: import org.cougaar.tools.csmart.core.cdata.ComponentData;
034: import org.cougaar.tools.csmart.core.property.BaseComponent;
035: import org.cougaar.tools.csmart.core.property.Property;
036: import org.cougaar.tools.csmart.experiment.HostComponent;
037: import org.cougaar.tools.csmart.experiment.NodeComponent;
038: import org.cougaar.tools.csmart.experiment.Experiment;
039: import org.cougaar.tools.csmart.recipe.MetricComponent;
040: import org.cougaar.tools.csmart.recipe.RecipeComponent;
041: import org.cougaar.tools.csmart.society.SocietyComponent;
042: import org.cougaar.tools.csmart.ui.viewer.CSMART;
043: import org.cougaar.tools.csmart.util.FileUtilities;
044: import org.cougaar.tools.csmart.util.ResultsFileFilter;
045: import org.cougaar.tools.server.OutputPolicy;
046: import org.cougaar.tools.server.RemoteFileSystem;
047: import org.cougaar.tools.server.RemoteHost;
048: import org.cougaar.util.log.Logger;
049:
050: import javax.swing.*;
051: import java.awt.*;
052: import java.io.BufferedReader;
053: import java.io.BufferedWriter;
054: import java.io.File;
055: import java.io.FileWriter;
056: import java.io.InputStream;
057: import java.io.InputStreamReader;
058: import java.text.DateFormat;
059: import java.text.SimpleDateFormat;
060: import java.util.ArrayList;
061: import java.util.Date;
062: import java.util.Observable;
063: import java.util.Properties;
064:
065: /**
066: * ClassName: NodeModel
067: * Description: The class contains all required data structures to
068: * represents a Node within CSMART. Modifications to key data can be
069: * monitored via a listener.
070: *
071: *
072: */
073: public class NodeModel extends Observable {
074: private NodeStatusButton statusButton;
075: private ConsoleStyledDocument doc;
076: private ConsoleTextPane textPane;
077: private ConsoleNodeListener listener;
078: private String logFileName;
079: private String nodeName;
080: private Logger log;
081: private NodeInfo info;
082: private CSMARTConsoleModel cmodel;
083: private String notifyCondition;
084: private boolean notifyOnStandardError = false; // if stderr appears, notify user
085: private ConsoleNodeOutputFilter displayFilter;
086: private OutputPolicy outputPolicy;
087: private CreateNodeThread thread;
088: // used for log file name
089: private static DateFormat fileDateFormat = new SimpleDateFormat(
090: "yyyyMMddHHmmss");
091: private String state;
092: public final static String STATE_INITTED = "NODE_STATE_INITTED";
093: public final static String STATE_STARTING = "NODE_STATE_STARTING";
094: public final static String STATE_RUNNING = "NODE_STATE_RUNNING";
095: public final static String STATE_STOPPING = "NODE_STATE_STOPPING";
096: public final static String STATE_STOPPED = "NODE_STATE_STOPPED";
097: public final static String STATE_ATTACHING = "NODE_STATE_ATTACHING";
098: public final static String STATE_UNATTACHED = "NODE_STATE_UNATTACHED";
099:
100: private NodeModel() {
101: // We don't want a generic constructor, so make it private.
102: }
103:
104: /**
105: * Constructs a new NodeModel object based on the informtion in <code>NodeInfo</code>.
106: *
107: * @param info A <code>NodeInfo</code> object containing required data for this node.
108: * @param cmodel <code>CSMARTConsoleModel</code> A pointer to the Console Model Object.
109: */
110: public NodeModel(NodeInfo info, CSMARTConsoleModel cmodel) {
111: this .info = info;
112: this .cmodel = cmodel;
113: this .nodeName = info.getNodeName();
114: createLogger();
115: statusButton = createStatusButton(info.getNodeName(), info
116: .getHostName());
117: doc = new ConsoleStyledDocument();
118: textPane = new ConsoleTextPane(doc, statusButton);
119: logFileName = FileUtilities.getLogFileName(info.getNodeName(),
120: new Date());
121: this .outputPolicy = new OutputPolicy(10);
122:
123: createFilters();
124: setState(STATE_INITTED);
125: }
126:
127: private void createLogger() {
128: log = CSMART.createLogger(this .getClass().getName());
129: }
130:
131: public void setState(String state) {
132: this .state = state;
133: setChanged();
134: notifyObservers(state);
135: }
136:
137: public String getState() {
138: return state;
139: }
140:
141: // create a node event listener to get events from the node
142: private void createListener() {
143: listener = new ConsoleNodeListener(this );
144: }
145:
146: // Set up Node filters & notifications
147: private void createFilters() {
148: this .notifyCondition = cmodel.getNotifyCondition();
149: if (notifyCondition != null) {
150: textPane.setNotifyCondition(notifyCondition);
151: }
152: ((ConsoleStyledDocument) textPane.getStyledDocument())
153: .setBufferSize(cmodel.getViewSize());
154: if (notifyOnStandardError) {
155: statusButton.getMyModel().setNotifyOnStandardError(true);
156: }
157: if (displayFilter != null) {
158: listener.setFilter(displayFilter);
159: }
160: }
161:
162: /**
163: * Sets a filter on the Console Node Output.
164: * @param filter The new <code>ConsoleNodeOutputFilter</code>
165: */
166: public void setFilter(ConsoleNodeOutputFilter filter) {
167: displayFilter = filter;
168: listener.setFilter(filter);
169: }
170:
171: /**
172: * Create a button representing a node.
173: */
174: private NodeStatusButton createStatusButton(String nodeName,
175: String hostName) {
176: NodeStatusButton button = new NodeStatusButton(
177: new ColoredCircle(NodeStatusButton.unknownStatus, 20,
178: null));
179: button.setSelectedIcon(new SelectedColoredCircle(
180: NodeStatusButton.unknownStatus, 20, null));
181: button.setToolTipText("Node " + nodeName + " (" + hostName
182: + "), unknown");
183: button.setActionCommand(nodeName);
184: button.setFocusPainted(false);
185: button.setBorderPainted(false);
186: button.setContentAreaFilled(false);
187: button.setMargin(new Insets(2, 2, 2, 2));
188: return button;
189: }
190:
191: /**
192: * Returns a handle to the NodeStatusButton for this Node.
193: *
194: * @return The <code>NodeStatusButton</code> for this Node
195: */
196: public NodeStatusButton getStatusButton() {
197: return this .statusButton;
198: }
199:
200: /**
201: *
202: * @return The Name of this Node
203: */
204: public String getNodeName() {
205: return nodeName;
206: }
207:
208: /**
209: *
210: * @return The Logfile for this node.
211: */
212: public String getLogFileName() {
213: return logFileName;
214: }
215:
216: /**
217: *
218: * @return A handle to the <code>ConsoleStyledDocument</code> for this Node.
219: */
220: public ConsoleStyledDocument getDoc() {
221: return doc;
222: }
223:
224: public NodeInfo getInfo() {
225: return info;
226: }
227:
228: public ConsoleTextPane getTextPane() {
229: return textPane;
230: }
231:
232: public ConsoleNodeListener getListener() {
233: return listener;
234: }
235:
236: public OutputPolicy getOutputPolicy() {
237: return outputPolicy;
238: }
239:
240: /**
241: * The CreateNodeThread must be created each time that we start
242: * or attach; threads can only be run once; they're not reusable.
243: */
244: public void start() {
245: setState(STATE_STARTING);
246: thread = new CreateNodeThread(cmodel, this );
247: createListener();
248: if (displayFilter != null)
249: listener.setFilter(displayFilter);
250: thread.start();
251: }
252:
253: public void attach() {
254: setState(STATE_ATTACHING);
255: thread = new CreateNodeThread(cmodel, this );
256: thread.setAttach(true);
257: createListener();
258: if (displayFilter != null)
259: listener.setFilter(displayFilter);
260: thread.start();
261: }
262:
263: /**
264: * Tell the node to stop.
265: */
266: public void stop() {
267: setState(STATE_STOPPING);
268: saveResults();
269: thread.interrupt();
270: thread.killNode();
271: }
272:
273: /**
274: * The node has stopped. Called by ConsoleNodeListener.
275: */
276: public void stopped() {
277: setState(STATE_STOPPED);
278: }
279:
280: /**
281: * Create a file for the results of this run.
282: * Results file structure is:
283: * <ExperimentName>
284: * Results-<Timestamp>.results
285: */
286: private void saveResults() {
287: String dirname = makeResultDirectory();
288: // Must check for null return here!?
289: if (dirname == null) {
290: // User didn't specify a directory or couldn't create one or something?
291: if (log.isInfoEnabled())
292: log
293: .info("saveResults got no good result directory from makeResult: Using pwd.");
294: // Is . really the right choice here?
295: dirname = ".";
296: }
297:
298: RemoteHost appServer = getInfo().getAppServer();
299: RemoteFileSystem remoteFS = null;
300: try {
301: remoteFS = appServer.getRemoteFileSystem();
302: } catch (Exception e) {
303: if (log.isErrorEnabled())
304: log.error("saveResults failed to get filesystem on "
305: + getInfo().getHostName() + ": ", e);
306: remoteFS = null;
307: }
308: if (remoteFS == null) {
309: final String host = getInfo().getHostName();
310: SwingUtilities.invokeLater(new Runnable() {
311: public void run() {
312: JOptionPane.showMessageDialog(null,
313: "Cannot save results. Unable to access filesystem for "
314: + host + ".",
315: "Unable to access file system",
316: JOptionPane.WARNING_MESSAGE);
317: }
318: });
319: } else {
320: copyResultFiles(remoteFS, dirname);
321: }
322: }
323:
324: /**
325: * Read remote files and copy to directory specified by experiment.
326: */
327: private void copyResultFiles(RemoteFileSystem remoteFS,
328: String dirname) {
329: char[] cbuf = new char[1000];
330: try {
331: // FIXME: This reads just from the current directory,
332: // but should read from wherever the BasicMetric told it to read,
333: // or in general, wherever the Component says to read
334: // But does the AppServer support calling list on arbitrary paths?
335: // See bug 1668
336: // Maybe to generalize, let this traverse sub-directories?
337: String[] filenames = remoteFS.list("./");
338: for (int i = 0; i < filenames.length; i++) {
339: if (!isResultFile(filenames[i]))
340: continue;
341: File newResultFile = new File(dirname + File.separator
342: + filenames[i]);
343: InputStream is = remoteFS.read(filenames[i]);
344: BufferedReader reader = new BufferedReader(
345: new InputStreamReader(is), 1000);
346: BufferedWriter writer = new BufferedWriter(
347: new FileWriter(newResultFile));
348: int len = 0;
349: while ((len = reader.read(cbuf, 0, 1000)) != -1) {
350: writer.write(cbuf, 0, len);
351: }
352: reader.close();
353: writer.close();
354: }
355: } catch (Exception e) {
356: if (log.isErrorEnabled()) {
357: log.error("NodeModel: copyResultFiles failed: ", e);
358: }
359: }
360: }
361:
362: /**
363: * This checks the society and recipes in the experiment to determine if
364: * any of them generated this metrics file.
365: * Creating a new File from the filename works because acceptFile
366: * just looks at the filename.
367: */
368: private boolean isResultFile(String filename) {
369: File this File = new java.io.File(filename);
370: // if no experiment, use default filter
371: if (cmodel.getExperiment() == null) {
372: return new ResultsFileFilter().accept(this File);
373: }
374: SocietyComponent societyComponent = cmodel.getExperiment()
375: .getSocietyComponent();
376: if (societyComponent != null) {
377: java.io.FileFilter fileFilter = societyComponent
378: .getResultFileFilter();
379: if (fileFilter != null && fileFilter.accept(this File)) {
380: return true;
381: }
382: }
383: int nrecipes = cmodel.getExperiment().getRecipeComponentCount();
384: for (int i = 0; i < nrecipes; i++) {
385: RecipeComponent recipeComponent = cmodel.getExperiment()
386: .getRecipeComponent(i);
387: if (recipeComponent instanceof MetricComponent) {
388: MetricComponent metricComponent = (MetricComponent) recipeComponent;
389: java.io.FileFilter fileFilter = metricComponent
390: .getResultFileFilter();
391: if (fileFilter != null && fileFilter.accept(this File)) {
392: return true;
393: }
394: }
395: }
396: return false;
397: }
398:
399: /**
400: * Create a directory for the results of this run.
401: * Results file structure is:
402: * <ExperimentName>
403: * Results-<Timestamp>.results
404: */
405: private String makeResultDirectory() {
406: // defaults, if we don't have an experiment
407: File resultDir = CSMART.getResultDir();
408: String experimentName = "Experiment";
409: if (cmodel.getExperiment() != null) {
410: resultDir = cmodel.getExperiment().getResultDirectory();
411: experimentName = cmodel.getExperiment().getExperimentName();
412: }
413: // if user didn't specify results directory, save in local directory
414: if (resultDir == null) {
415: if (log.isInfoEnabled())
416: log
417: .info("No result directory specified. Should use a local dir. Returning null (in makeResultDirectory).");
418: return null;
419: }
420: String dirname = resultDir.getAbsolutePath()
421: + File.separatorChar + experimentName
422: + File.separatorChar + "Results-"
423: + fileDateFormat.format(cmodel.getRunStart());
424: try {
425: File f = new File(dirname);
426: // guarantee that directories exist
427: if (!f.exists() && !f.mkdirs() && !f.exists()) {
428: if (log.isWarnEnabled())
429: log
430: .warn("Unabled to create directory "
431: + dirname
432: + ". Should default to local directory - returning null (in makeResultDirectory)");
433: return null;
434: }
435: } catch (Exception e) {
436: if (log.isErrorEnabled()) {
437: log.error("Couldn't create results directory "
438: + dirname + ": ", e);
439: }
440: return null;
441: }
442: return dirname;
443: }
444:
445: /**
446: * Get host properties from experiment if it exists.
447: */
448: public Object getHostPropertyValue(String hostName,
449: String propertyName) {
450: Experiment experiment = cmodel.getExperiment();
451: if (experiment == null)
452: return null;
453: HostComponent[] hosts = experiment.getHostComponents();
454: for (int i = 0; i < hosts.length; i++) {
455: String s = hosts[i].getShortName();
456: if (s.equalsIgnoreCase(hostName))
457: return getPropertyValue(hosts[i], propertyName);
458: }
459: return null;
460: }
461:
462: /**
463: * Get node properties from experiment if it exists.
464: */
465: protected Object getNodePropertyValue(String nodeName,
466: String propertyName) {
467: Experiment experiment = cmodel.getExperiment();
468: if (experiment == null)
469: return null;
470: HostComponent[] hosts = experiment.getHostComponents();
471: for (int i = 0; i < hosts.length; i++) {
472: NodeComponent[] nodes = hosts[i].getNodes();
473: for (int j = 0; j < nodes.length; j++) {
474: String s = nodes[j].getShortName();
475: if (s.equals(nodeName))
476: return getPropertyValue(nodes[j], propertyName);
477: }
478: }
479: return null;
480: }
481:
482: private Object getPropertyValue(BaseComponent component, String name) {
483: Property prop = component.getProperty(name);
484: if (prop == null)
485: return null;
486: return prop.getValue();
487: }
488:
489: /**
490: * Get description of agents.
491: * Returns an array of strings.
492: */
493: public ArrayList getAgentComponentDescriptions(String nodeName,
494: String agentName) {
495: Experiment experiment = cmodel.getExperiment();
496: if (experiment == null)
497: return null;
498: ComponentData societyComponentData = experiment
499: .getSocietyComponentData();
500: if (societyComponentData == null) {
501: if (log.isWarnEnabled()) {
502: log.warn("NodeModel: Need to save experiment");
503: }
504: return null;
505: }
506: ComponentData[] children = societyComponentData.getChildren();
507: ComponentData nodeComponentData = null;
508: for (int i = 0; i < children.length; i++) {
509: if (children[i].getType().equals(ComponentData.HOST)) {
510: ComponentData[] nodes = children[i].getChildren();
511: for (int j = 0; j < nodes.length; j++) {
512: if (nodes[j].getName().equals(nodeName)) {
513: nodeComponentData = nodes[j];
514: break;
515: }
516: }
517: }
518: }
519:
520: // If couldn't find the node in the ComponentData, give up
521: if (nodeComponentData == null)
522: return null;
523:
524: ComponentData agentComponentData = null;
525:
526: // The "agent" might be a NodeAgent, in which case this is the right spot.
527: if (agentName.equals(nodeComponentData.getName())) {
528: agentComponentData = nodeComponentData;
529: } else {
530: // OK. Find the sub-Agent with the right name
531: ComponentData[] agents = nodeComponentData.getChildren();
532: for (int i = 0; i < agents.length; i++) {
533: if (agents[i] instanceof AgentComponentData
534: && agents[i].getName().equals(agentName)) {
535: agentComponentData = agents[i];
536: break;
537: }
538: }
539: }
540:
541: // If couldn't find the Agent in the ComponentData for the node, give up
542: if (agentComponentData == null)
543: return null;
544:
545: // Loop through the children
546: ComponentData[] agentChildren = agentComponentData
547: .getChildren();
548: ArrayList entries = new ArrayList(agentChildren.length);
549: for (int i = 0; i < agentChildren.length; i++) {
550:
551: // If this Agent is a NodeAgent, ignore its Agent children.
552: if (agentChildren[i].getType().equals(ComponentData.AGENT))
553: continue;
554:
555: // FIXME: This should use same code as ExperimentINIWriter if possible
556: StringBuffer sb = new StringBuffer();
557: if (agentChildren[i].getType().equals(
558: ComponentData.AGENTBINDER)) {
559: sb.append(PluginManager.INSERTION_POINT + ".Binder");
560: } else if (agentChildren[i].getType().equals(
561: ComponentData.NODEBINDER)) {
562: sb.append(AgentManager.INSERTION_POINT + ".Binder");
563: } else {
564: sb.append(agentChildren[i].getType());
565: }
566: if (ComponentDescription.parsePriority(agentChildren[i]
567: .getPriority()) != ComponentDescription.PRIORITY_COMPONENT) {
568: sb.append("(" + agentChildren[i].getPriority() + ")");
569: }
570: sb.append(" = ");
571: sb.append(agentChildren[i].getClassName());
572: if (agentChildren[i].parameterCount() != 0) {
573: sb.append("(");
574: Object[] params = agentChildren[i].getParameters();
575: sb.append(params[0].toString());
576: for (int j = 1; j < agentChildren[i].parameterCount(); j++) {
577: sb.append(",");
578: sb.append(params[j].toString());
579: }
580: sb.append(")");
581: }
582: entries.add(sb.toString());
583: }
584: return entries;
585: }
586:
587: /**
588: * Restart is the same as start, except we first clear the
589: * persistent data.
590: */
591: public void restart() {
592: // remove the persistent data
593: Properties properties = info.getProperties();
594: properties.remove(Experiment.PERSIST_CLEAR);
595: start();
596: }
597:
598: public void setNotification(String notifyCondition,
599: boolean notifyOnStdErr) {
600: textPane.setNotifyCondition(notifyCondition);
601: statusButton.getMyModel().clearError();
602: statusButton.getMyModel().setNotifyOnStandardError(
603: notifyOnStdErr);
604: }
605:
606: }
|