001: /*
002: * <copyright>
003: *
004: * Copyright 1997-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.glm.servlet;
028:
029: import java.io.IOException;
030: import java.io.Serializable;
031: import java.util.Collection;
032: import java.util.Date;
033: import java.util.Enumeration;
034: import java.util.HashMap;
035: import java.util.HashSet;
036: import java.util.Iterator;
037: import java.util.Map;
038: import java.util.Set;
039:
040: import javax.servlet.ServletException;
041: import javax.servlet.http.HttpServletRequest;
042: import javax.servlet.http.HttpServletResponse;
043:
044: import org.cougaar.core.blackboard.IncrementalSubscription;
045: import org.cougaar.core.blackboard.SubscriptionWatcher;
046: import org.cougaar.core.service.BlackboardService;
047: import org.cougaar.core.service.SchedulerService;
048: import org.cougaar.core.servlet.SimpleServletSupport;
049: import org.cougaar.glm.ldm.Constants;
050: import org.cougaar.glm.parser.GLMTaskParser;
051: import org.cougaar.lib.util.UTILAllocate;
052: import org.cougaar.planning.ldm.plan.NewPrepositionalPhrase;
053: import org.cougaar.planning.ldm.plan.NewTask;
054: import org.cougaar.planning.ldm.plan.PlanElement;
055: import org.cougaar.planning.ldm.plan.Task;
056: import org.cougaar.planning.servlet.BlackboardServletSupport;
057: import org.cougaar.planning.servlet.ServletWorker;
058: import org.cougaar.planning.servlet.data.xml.XMLWriter;
059: import org.cougaar.planning.servlet.data.xml.XMLable;
060: import org.cougaar.util.DynamicUnaryPredicate;
061: import org.cougaar.util.SyncTriggerModelImpl;
062: import org.cougaar.util.Trigger;
063: import org.cougaar.util.TriggerModel;
064:
065: /**
066: * <pre>
067: * One created for every URL access.
068: *
069: * If either waitBefore or waitAfter is true, uses a blackboard
070: * service, a watcher, and a trigger to monitor published tasks to see
071: * when they are complete.
072: * </pre>
073: **/
074: public class GLMStimulatorWorker extends ServletWorker {
075:
076: /** no batch interval can be less than this number */
077: private static long MIN_INTERVAL = 20l; // millis
078:
079: /**
080: * <pre>
081: * Here is our inner class that will handle all HTTP and
082: * HTTPS service requests.
083: *
084: * If we should wait until the batch is complete, we make a watcher to watch
085: * the blackboard, a trigger that will call blackboardChanged (), and a trigger model
086: * to connect them.
087: *
088: * </pre>
089: * @see #blackboardChanged
090: */
091: public void execute(HttpServletRequest request,
092: HttpServletResponse response, SimpleServletSupport support)
093: throws IOException, ServletException {
094: this .support = (BlackboardServletSupport) support;
095:
096: Enumeration params = request.getParameterNames();
097: for (; params.hasMoreElements();) {
098: String name = (String) params.nextElement();
099: String value = request.getParameter(name);
100: getSettings(name, value);
101: }
102:
103: if (support.getLog().isDebugEnabled())
104: support.getLog().debug("GLMStimulatorWorker.Invoked...");
105:
106: if (waitBefore || waitAfter) {
107: // create a blackboard watcher
108: watcher = new SubscriptionWatcher() {
109: public void signalNotify(int event) {
110: // gets called frequently as the blackboard objects change
111: super .signalNotify(event);
112: tm.trigger();
113: }
114:
115: public String toString() {
116: return "ThinWatcher("
117: + GLMStimulatorWorker.this .toString() + ")";
118: }
119: };
120:
121: // create a callback for running this component
122: Trigger myTrigger = new Trigger() {
123: // no need to "sync" when using "SyncTriggerModel"
124: public void trigger() {
125: watcher.clearSignal();
126: blackboardChanged();
127: }
128:
129: public String toString() {
130: return "Trigger("
131: + GLMStimulatorWorker.this .toString() + ")";
132: }
133: };
134:
135: this .tm = new SyncTriggerModelImpl(this .support
136: .getSchedulerService(), myTrigger);
137: this .support.getBlackboardService().registerInterest(
138: watcher);
139: this .support.getBlackboardService().openTransaction();
140: planElementSubscription = (IncrementalSubscription) this .support
141: .getBlackboardService().subscribe(
142: planElementPredicate);
143: this .support.getBlackboardService().closeTransaction();
144: // activate the trigger model
145: tm.initialize();
146: tm.load();
147: tm.start();
148: }
149:
150: // returns an "XMLable" result.
151: XMLable result = getResult();
152:
153: if (result != null)
154: writeResponse(result, response.getOutputStream(), request,
155: support, format);
156: if (waitBefore || waitAfter) {
157: BlackboardService bb = this .support.getBlackboardService();
158: bb.openTransaction();
159:
160: if (rescindAfterComplete) {
161: for (Iterator i = rescindTasks.iterator(); i.hasNext();) {
162: bb.publishRemove(i.next());
163: i.remove();
164: }
165: }
166:
167: rescindTasks.clear();
168:
169: bb.unsubscribe(planElementSubscription);
170: bb.closeTransaction();
171: bb.unregisterInterest(watcher);
172:
173: tm.unload();
174: tm.stop();
175: }
176: }
177:
178: // unused
179: protected String getPrefix() {
180: return "GLMStimulator at ";
181: }
182:
183: /**
184: * <pre>
185: * Use a query parameter to set a field
186: *
187: * Sets the recognized parameters : inputFile, debug, totalBatches, tasksPerBatch, interval, and wait
188: * </pre>
189: */
190: public void getSettings(String name, String value) {
191: super .getSettings(name, value);
192:
193: if (support.getLog().isDebugEnabled())
194: support.getLog().debug(
195: "GLMStimulatorWorker.getSettings - name " + name
196: + " value " + value);
197:
198: if (eq(name, GLMStimulatorServlet.INPUT_FILE))
199: inputFile = value;
200: else if (eq(name, GLMStimulatorServlet.FOR_PREP)) {
201: String tmp = (value != null ? value.trim() : "");
202: forPrep = (tmp.length() > 0 ? tmp : null);
203: } else if (eq(name, "debug"))
204: debug = eq(value, "true");
205: else if (eq(name, GLMStimulatorServlet.NUM_BATCHES))
206: totalBatches = Integer.parseInt(value);
207: else if (eq(name, GLMStimulatorServlet.TASKS_PER_BATCH))
208: tasksPerBatch = Integer.parseInt(value);
209: else if (eq(name, GLMStimulatorServlet.INTERVAL)) {
210: try {
211: interval = Long.parseLong(value);
212: if (interval < MIN_INTERVAL)
213: interval = MIN_INTERVAL;
214: } catch (Exception e) {
215: interval = 1000l;
216: }
217: } else if (eq(name, GLMStimulatorServlet.WAIT_BEFORE)) {
218: waitBefore = eq(value, "true");
219: } else if (eq(name, GLMStimulatorServlet.WAIT_AFTER)) {
220: waitAfter = eq(value, "true");
221: } else if (eq(name, GLMStimulatorServlet.RESCIND_AFTER_COMPLETE)) {
222: rescindAfterComplete = eq(value, "true");
223: } else if (eq(name, GLMStimulatorServlet.USE_CONFIDENCE)) {
224: useConfidence = eq(value, "true");
225: } else if (eq(name, GLMStimulatorServlet.TASK_PARSER_CLASS)) {
226: taskParserClass = value;
227: }
228: }
229:
230: /**
231: * When the rescind task button is pressed, rescind the task.
232: *
233: * @param label provides way to give feedback
234: */
235: /*
236: protected void rescindTasks (JLabel label) {
237: if (sentTasks.size() == 0){
238: label.setText("No tasks to Rescind.");
239: } else {
240: try {
241: support.getBlackboardService().openTransaction();
242: Iterator iter = sentTasks.iterator ();
243: Object removed = iter.next ();
244: iter.remove ();
245:
246: if (debug)
247: System.out.println ("GLMStimulatorWorker - Removing " + removed);
248: publishRemove(removed);
249: sentTasks.remove(removed);
250: label.setText("Rescinded last task. " + sentTasks.size () + " left.");
251: }catch (Exception exc) {
252: System.err.println(exc.getMessage());
253: exc.printStackTrace();
254: } finally{
255: support.getBlackboardService().closeTransactionDontReset();
256: }
257: }
258: }
259: */
260:
261: /**
262: * Main work done here. <p>
263: *
264: * Sends the first batch of tasks, and keeps sending until totalBatches have been
265: * sent. If should wait for completion, waits until notified by blackboardChanged (). <p>
266: *
267: * Will wait <b>interval</b> milliseconds between batches if there are
268: * more than one batches to send.
269: *
270: * @see #getSettings
271: * @see #blackboardChanged
272: * @return an "XMLable" result.
273: */
274: protected XMLable getResult() {
275: // Get name of XML data file
276: if (inputFile == null
277: || inputFile.equals("")
278: || support.getConfigFinder().locateFile(inputFile) == null) {
279: support.getLog().error(
280: "GLMStimulatorWorker Could not find the file ["
281: + inputFile + "]");
282: return new Message(inputFile);
283: }
284:
285: testStart = System.currentTimeMillis(); // First send is immediate
286: nextSendTime = testStart;
287:
288: while (batchesSent < totalBatches) {
289: if (support.getLog().isDebugEnabled())
290: support.getLog().debug(
291: "GLMStimulatorWorker.getResult - batches so far "
292: + batchesSent + " < total "
293: + totalBatches + " sentTasks "
294: + sentTasks.size());
295: if (waitBefore) {
296: synchronized (sentTasks) {
297: while (!sentTasks.isEmpty()) {
298: // Wait for previously sent tasks to complete
299: try {
300: if (support.getLog().isDebugEnabled())
301: support
302: .getLog()
303: .debug(
304: "GLMStimulatorWorker.getResult - waiting for blackboard to notify.");
305:
306: sentTasks.wait();
307: } catch (Exception e) {
308: }
309: }
310: }
311: }
312: long waitTime = nextSendTime - System.currentTimeMillis();
313: if (waitTime > 0L) {
314: // Need to wait a while
315: if (support.getLog().isDebugEnabled())
316: support.getLog().debug(
317: "GLMStimulatorWorker.getResult - waiting wait time "
318: + waitTime);
319: try {
320: Thread.sleep(waitTime);
321: } catch (Exception e) {
322: }
323: }
324: sendNextBatch(true);
325: nextSendTime += interval;
326: }
327:
328: if (waitAfter || waitBefore) {
329: synchronized (sentTasks) {
330: while (!sentTasks.isEmpty()) {
331: try {
332: if (support.getLog().isDebugEnabled())
333: support
334: .getLog()
335: .debug(
336: "GLMStimulatorWorker.getResult - waiting for tasks to complete.");
337: sentTasks.wait();
338: } catch (Exception e) {
339: }
340: }
341: }
342: }
343:
344: return responseData;
345: }
346:
347: /** tiny little class for sending back a message when it can't find the file */
348: private static class Message implements XMLable, Serializable {
349: String file;
350:
351: public Message(String file) {
352: this .file = file;
353: }
354:
355: public void toXML(XMLWriter w) throws IOException {
356: w.tagln("Error", "Couldn't find file " + file
357: + ". Check path, try again.");
358: }
359: }
360:
361: /**
362: * Publishes the tasks created by readXmlTasks. <p>
363: *
364: * For each task, adds to sentTasks map of task to its send time.
365: * sentTasks is used later by blackboardChanged to determine how long the
366: * task took to complete.
367: *
368: * @see #readXmlTasks
369: * @see #blackboardChanged
370: * @param withinTransaction - true when called from handleSuccessfulPlanElement
371: * this avoids having nested transactions
372: */
373: protected void sendNextBatch(boolean withinTransaction) {
374: Date batchStart = new Date();
375:
376: if (support.getLog().isDebugEnabled())
377: support.getLog().debug(
378: "GLMStimulatorWorker.sendTasks - batch start "
379: + batchStart);
380:
381: batchesSent++;
382:
383: try {
384: if (withinTransaction)
385: support.getBlackboardService().openTransaction();
386: for (int i = 0; i < tasksPerBatch; i++) {
387: // Get the tasks out of the XML file
388: Collection theseTasks = readXmlTasks(inputFile);
389: for (Iterator it = theseTasks.iterator(); it.hasNext();) {
390: Task task = (Task) it.next();
391: if (forPrep != null) {
392: NewTask nt = (NewTask) task;
393: NewPrepositionalPhrase npp = support.getLDMF()
394: .newPrepositionalPhrase();
395: npp.setPreposition(Constants.Preposition.FOR);
396: npp.setIndirectObject(forPrep);
397: nt.addPrepositionalPhrase(npp);
398: }
399: sentTasks.put(task, batchStart);
400: support.getBlackboardService().publishAdd(task);
401: }
402: }
403: } catch (Exception exc) {
404: support.getLog().error("Could not next batch", exc);
405: } finally {
406: if (withinTransaction)
407: support.getBlackboardService()
408: .closeTransactionDontReset();
409: }
410: }
411:
412: /**
413: * Parse the xml file and return the COUGAAR tasks.
414: *
415: * @param xmlTaskFile file defining tasks to stimulate cluster with
416: * @return Collection of tasks defined in xml file
417: */
418: protected Collection readXmlTasks(String xmlTaskFile) {
419: Collection tasks = null;
420: try {
421: Class actualTaskParserClass = Class
422: .forName(taskParserClass);
423: GLMTaskParser taskParser = (GLMTaskParser) actualTaskParserClass
424: .newInstance();
425: taskParser.init(xmlTaskFile, support.getLDMF(), support
426: .getAgentIdentifier(), support.getConfigFinder(),
427: support.getLDM(), support.getLog());
428: tasks = UTILAllocate.enumToList(taskParser.getTasks());
429: } catch (ClassNotFoundException cnfe) {
430: support.getLog().error(
431: "Could not find class <" + taskParserClass + ">",
432: cnfe);
433: } catch (InstantiationException iae) {
434: support
435: .getLog()
436: .error(
437: "Could not make instance of <"
438: + taskParserClass
439: + ">. Is there a no-arg constructor?",
440: iae);
441: } catch (Exception ex) {
442: support.getLog().error(
443: "Error parsing xml task file " + xmlTaskFile
444: + " with task parser " + taskParserClass,
445: ex);
446: }
447: return tasks;
448: }
449:
450: /**
451: * Called when one of the tasks that was added to the blackboard has
452: * it's plan element change. If the task has been successfully
453: * disposed, it is removed from the sentTasks Map.
454: **/
455: protected void blackboardChanged() {
456: try {
457: support.getBlackboardService().openTransaction();
458: Set toRemove = new HashSet(); // to avoid concurrent mod error
459: if (planElementSubscription.hasChanged()) {
460: boolean wasEmpty = true;
461: Collection changedItems = planElementSubscription
462: .getChangedCollection();
463: synchronized (sentTasks) {
464: for (Iterator iter2 = changedItems.iterator(); iter2
465: .hasNext();) {
466: if (support.getLog().isDebugEnabled())
467: support
468: .getLog()
469: .debug(
470: "GLMStimulatorWorker.blackboard changed - found changed plan elements.");
471: wasEmpty = false;
472: PlanElement pe = (PlanElement) iter2.next();
473:
474: if (support.getLog().isDebugEnabled())
475: support.getLog()
476: .debug(
477: "GLMStimulatorWorker.blackboard PE "
478: + pe.getUID()
479: + " changed.");
480:
481: Task task = pe.getTask();
482: Date timeSent = (Date) sentTasks.get(task);
483: if (timeSent != null) {
484: recordTime(task.getUID().toString(),
485: timeSent);
486: }
487: sentTasks.remove(task);
488: rescindTasks.add(task);
489: }
490: if (support.getLog().isDebugEnabled())
491: support
492: .getLog()
493: .debug(
494: "GLMStimulatorWorker.blackboard changed - notifying.");
495: synchronized (this ) {
496: sentTasks.notify();
497: }
498: }
499: }
500: } catch (Exception exc) {
501: support.getLog().error("Could not publish tasks.", exc);
502: } finally {
503: support.getBlackboardService().closeTransactionDontReset();
504: }
505: }
506:
507: /** records the time taken for the task */
508: protected void recordTime(String taskUID, Date sentTime) {
509: // Print timing information for the completed batch
510: long now = System.currentTimeMillis();
511: long elapsed = now - sentTime.getTime();
512: String t = getElapsedTime(elapsed);
513: String total = getElapsedTime(now - testStart);
514:
515: if (support.getLog().isDebugEnabled())
516: support.getLog().debug(
517: "*** Testing batch #"
518: + (responseData.taskTimes.size() + 1)
519: + " completed in " + t + " total " + total);
520:
521: // Cache the timing information
522: responseData.addTaskAndTime(taskUID, t, elapsed);
523: }
524:
525: /** encodes a time interval in a min:sec:millis format */
526: protected String getElapsedTime(long diff) {
527: long min = diff / 60000l;
528: long sec = (diff - (min * 60000l)) / 1000l;
529: long millis = diff - (min * 60000l) - (sec * 1000l);
530: return min + ":" + ((sec < 10) ? "0" : "") + sec + ":"
531: + ((millis < 10) ? "00" : ((millis < 100) ? "0" : ""))
532: + millis;
533: }
534:
535: /** record the elapsed time */
536: protected void printTestingSummary() {
537: String totalTime = getElapsedTime(System.currentTimeMillis()
538: - testStart);
539: responseData.totalTime = totalTime;
540:
541: if (support.getLog().isDebugEnabled())
542: support.getLog().debug(responseData.toString());
543: }
544:
545: // rely upon load-time introspection to set these services -
546: // don't worry about revokation.
547: public final void setSchedulerService(SchedulerService ss) {
548: scheduler = ss;
549: }
550:
551: /**
552: * Matches PlanElements for tasks that we have sent and which are
553: * complete, i.e. have plan elements and 100% confident reported
554: * allocation results. Both Failure and Success are accepted.
555: **/
556: private DynamicUnaryPredicate planElementPredicate = new DynamicUnaryPredicate() {
557: public boolean execute(Object o) {
558: if (o instanceof PlanElement) {
559: PlanElement pe = (PlanElement) o;
560: Task task = pe.getTask();
561: if (sentTasks.containsKey(task)) {
562: if (useConfidence) {
563: boolean hasReported = (pe.getReportedResult() != null);
564: if (!hasReported)
565: return false;
566: boolean highConfidence = (pe
567: .getReportedResult()
568: .getConfidenceRating() >= UTILAllocate.HIGHEST_CONFIDENCE);
569: if (!highConfidence)
570: return false;
571: }
572: return true; // Ignore confidence for now.
573: }
574: }
575: return false;
576: }
577: };
578:
579: /**
580: * Subscription to PlanElements disposing of our tasks
581: **/
582: private IncrementalSubscription planElementSubscription;
583:
584: /** Collection of tasks that have been sent. Needed for later rescinds */
585: protected Map sentTasks = new HashMap();
586: protected Set rescindTasks = new HashSet();
587:
588: /** for waiting for a subscription on the blackboard */
589: protected SubscriptionWatcher watcher;
590: /** for waiting for a subscription on the blackboard */
591: protected TriggerModel tm;
592: /** for waiting for a subscription on the blackboard */
593: private SchedulerService scheduler;
594:
595: /** start of the whole test */
596: protected long testStart;
597:
598: /** returned response */
599: protected GLMStimulatorResponseData responseData = new GLMStimulatorResponseData();
600:
601: /** batches sent so far */
602: protected int batchesSent = 0;
603: /** tasks per batch */
604: protected int tasksPerBatch = 1;
605: /** total batches requested */
606: protected int totalBatches = 0;
607: /** millis between batches */
608: protected long interval;
609: /** dump debug output if true */
610: protected boolean debug = false;
611:
612: /** wait for completion before publishing next batch */
613: protected boolean waitBefore = false;
614:
615: /** wait for completion after publishing everything */
616: protected boolean waitAfter = false;
617:
618: /** when was the last batch sent */
619: protected long nextSendTime;
620:
621: /** use confidence to determine when task is complete */
622: protected boolean useConfidence;
623:
624: /** remove the injected tasks after they have been completed */
625: protected boolean rescindAfterComplete;
626:
627: /** option override of task FOR preposition */
628: protected String forPrep = null;
629:
630: protected String inputFile = " ";
631: protected String taskParserClass;
632:
633: protected BlackboardServletSupport support;
634: }
|