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.planning.ldm.plan;
028:
029: import java.io.IOException;
030: import java.io.ObjectInputStream;
031: import java.io.ObjectOutputStream;
032: import java.util.ArrayList;
033: import java.util.Collections;
034: import java.util.Enumeration;
035: import java.util.HashSet;
036: import java.util.List;
037: import java.util.Set;
038: import java.util.Vector;
039:
040: import org.cougaar.core.blackboard.ClaimableImpl;
041: import org.cougaar.core.mts.MessageAddress;
042: import org.cougaar.core.util.UID;
043: import org.cougaar.util.Empty;
044: import org.cougaar.util.log.Logger;
045: import org.cougaar.util.log.Logging;
046:
047: /**
048: * This class implements Workflow
049: **/
050:
051: public class WorkflowImpl extends ClaimableImpl implements Workflow,
052: NewWorkflow, java.io.Serializable {
053: private static final Logger logger = Logging
054: .getLogger(WorkflowImpl.class);
055: static final long serialVersionUID = -8610461428992212L;
056:
057: private transient Task basetask;
058: // protected access for MPWorkflowImpl
059: protected transient Vector subtasks = new Vector();
060: private transient Vector constraints = new Vector();
061: private UID uid;
062: private MessageAddress owner;
063: private transient AllocationResultAggregator currentARA = AllocationResultAggregator.DEFAULT;
064: private transient AllocationResult cachedar = null;
065: private transient int walkingSubtasks = 0;
066: private transient Set changedSubtasks = new HashSet(3);
067:
068: public void setWalkingSubtasks(boolean walking) {
069: if (walking) {
070: walkingSubtasks++;
071: } else if (walkingSubtasks > 0) {
072: walkingSubtasks--;
073: }
074: }
075:
076: public String toString() {
077: return "<workflow " + uid + " of base task " + basetask
078: + " of " + subtasks.size() + " tasks " + "and "
079: + constraints.size() + " constraints>";
080: }
081:
082: public WorkflowImpl(MessageAddress owner, UID uid) {
083: this .owner = owner;
084: this .uid = uid;
085: }
086:
087: /**@return Task for which this Workflow is an expansion */
088: public Task getParentTask() {
089: return basetask;
090: }
091:
092: /**
093: * Get an Enumeration of all subtasks. The subtasks of a workflow is
094: * volatile and subject to change at any time (due to rescind of the
095: * expansion) to guard against such volatility, the use of the
096: * returned Enumeration should be protected by synchronizing on the
097: * workflow. E.g.:<pre>
098: * synchronized (wf) {
099: * Enumeration en = wf.getTasks();
100: * // Use the en
101: * }</pre>
102: * The code using the enumeration should be brief to avoid locking the
103: * workflow for an extended period of time.
104: * @return Enumeration{Task} Enumerate over our subtasks.
105: **/
106: public Enumeration getTasks() {
107: assert Thread.holdsLock(this );
108: return subtasks.elements();
109: }
110:
111: /** @return Enumeration{Constraint} Enumerate over the constraints. */
112: public Enumeration getConstraints() {
113: return constraints.elements();
114: }
115:
116: /**
117: *@param task - task which you are inquiring about
118: *@return Enumeration{Constraint} Enumerate over constraints that related to the passed in task. */
119: public Enumeration getTaskConstraints(Task task) {
120: Enumeration c = constraints.elements();
121: Vector contasks = new Vector();
122: while (c.hasMoreElements()) {
123: Constraint ct = (Constraint) c.nextElement();
124: if ((task.equals(ct.getConstrainedTask()))
125: || (task.equals(ct.getConstrainingTask()))) {
126: contasks.addElement(ct);
127: }
128: }
129: return contasks.elements();
130: }
131:
132: /** @param constrainedTask - the task being constrained
133: * @param constrainingTask - the task that is constraining another task
134: *@return Enumeration{Constraints} - Constraints that are related to both tasks */
135: public Enumeration getPairConstraints(Task constrainedTask,
136: Task constrainingTask) {
137:
138: Enumeration c = constraints.elements();
139: Vector contasks = new Vector();
140: while (c.hasMoreElements()) {
141: Constraint ct = (Constraint) c.nextElement();
142: if ((constrainedTask.equals(ct.getConstrainedTask()))
143: && (constrainingTask.equals(ct
144: .getConstrainingTask()))) {
145: contasks.addElement(ct);
146: }
147: }
148: return contasks.elements();
149: }
150:
151: private transient TaskScoreTable _tst = null;
152:
153: private void clearTST() {
154: _tst = null;
155: }
156:
157: private TaskScoreTable updateTST() {
158: assert Thread.holdsLock(this ); // redundant - only called from aggregateAllocationResults
159: if (_tst == null) {
160: int n = subtasks.size();
161: if (n == 0)
162: return null;
163: Task[] _tasks = (Task[]) subtasks.toArray(new Task[n]);
164: _tst = new TaskScoreTable(_tasks);
165: }
166: for (int i = 0, n = _tst.size(); i < n; i++) {
167: Task task = _tst.getTask(i);
168: PlanElement pe = task.getPlanElement();
169: if (pe != null) {
170: _tst.setAllocationResult(i, pe.getEstimatedResult());
171: }
172: }
173: return _tst;
174: }
175:
176: /** Calls calculate on the defined AllocationResultAggregator
177: * @return a new AllocationResult representing aggregation of
178: * all subtask results
179: */
180: public AllocationResult aggregateAllocationResults() {
181: return aggregateAllocationResults(Collections.EMPTY_LIST);
182: }
183:
184: /**
185: * This variant is used by the infrastructure
186: * (ReceiveNotificationLP) to record the list of changed subtasks
187: * contributing to the new allocation result.
188: **/
189: public synchronized AllocationResult aggregateAllocationResults(
190: List changedSubtaskUIDs) {
191: TaskScoreTable tst = updateTST();
192: if (tst == null)
193: return null;
194: // call calculate on the PenaltyValueAggregator
195: AllocationResult newresult = currentARA.calculate(this , tst,
196: cachedar);
197: cachedar = newresult;
198: changedSubtasks.addAll(changedSubtaskUIDs);
199: return newresult;
200: }
201:
202: /**
203: * get the latest copy of the allocationresults for each subtask.
204: * Information is stored in a List which contains a SubTaskResult for each subtask.
205: * Each of the SubTaskResult objects contain the following information:
206: * Task - the subtask, boolean - whether the result changed,
207: * AllocationResult - the result used by the aggregator for this sub-task.
208: * The boolean indicates whether the AllocationResult changed since the
209: * last time the collection was cleared by the plugin (which should be
210: * the last time the plugin looked at the list).
211: * @return List of SubTaskResultObjects one for each subtask
212: **/
213: public synchronized SubtaskResults getSubtaskResults() {
214: // assert Thread.holdsLock(this); // true by definition
215: int n = subtasks.size();
216: SubtaskResults result = new SubtaskResults(n, cachedar);
217: for (int i = 0; i < n; i++) {
218: Task task = (Task) subtasks.get(i);
219: UID uid = task.getUID();
220: boolean changed = changedSubtasks.contains(uid);
221: AllocationResult ar;
222: PlanElement pe = task.getPlanElement();
223: if (pe == null) {
224: ar = null;
225: } else {
226: ar = pe.getEstimatedResult();
227: }
228: result.add(new SubTaskResult(task, changed, ar));
229: }
230: changedSubtasks.clear();
231: return result;
232: }
233:
234: /** Has a constraint been violated?
235: **/
236: public boolean constraintViolation() {
237: for (Enumeration cons = constraints.elements(); cons
238: .hasMoreElements();) {
239: Constraint c = (Constraint) cons.nextElement();
240: if (isConstraintViolated(c)) {
241: return true;
242: }
243: }
244: return false;
245: }
246:
247: /** Get the constraints that were violated.
248: * @return Enumeration{Constraint}
249: */
250:
251: public Enumeration getViolatedConstraints() {
252: Vector violations = getViolatedConstraintsVector(false);
253: if (violations == null || violations.size() == 0) {
254: return Empty.enumeration;
255: }
256: return violations.elements();
257: }
258:
259: private Vector getViolatedConstraintsVector(boolean firstOnly) {
260: // check to see if there are any constraints
261: if (constraints.size() == 0)
262: return null;
263: Vector violations = new Vector(constraints.size());
264: for (Enumeration cons = constraints.elements(); cons
265: .hasMoreElements();) {
266: Constraint c = (Constraint) cons.nextElement();
267: if (isConstraintViolated(c)) {
268: violations.addElement(c);
269: if (firstOnly)
270: break;
271: }
272: }
273: return violations;
274: }
275:
276: //setter method implementations from NewWorkflow
277:
278: /** @param parentTask set the parent task */
279: public void setParentTask(Task parentTask) {
280: basetask = parentTask;
281: }
282:
283: /** @param tasks set the tasks of the Workflow */
284: public synchronized void setTasks(Enumeration tasks) {
285: if (tasks == null) {
286: throw new IllegalArgumentException(
287: "Workflow.setTasks(Enum e): e must be a non-null Enumeration");
288: }
289: if (walkingSubtasks > 0) {
290: RuntimeException rt = new RuntimeException(
291: "Attempt to remove subtasks while enum is active");
292: rt.printStackTrace();
293: }
294:
295: subtasks.removeAllElements();
296: while (tasks.hasMoreElements()) {
297: Task t = (Task) tasks.nextElement();
298: if (t != null) {
299: subtasks.addElement(t);
300: } else {
301: // buzzz... wrong answer - tried to pass in a null!
302: throw new IllegalArgumentException(
303: "Workflow.setTasks(Enum e): all elements of e must be Tasks");
304: }
305: }
306: changedSubtasks.clear();
307: clearTST();
308: }
309:
310: /** @param newTask addTask allows you to add a Task to a Workflow.*/
311: public synchronized void addTask(Task newTask) {
312: if (newTask == null) {
313: // buzzzz wrong answer - tried to pass in a null!!
314: throw new IllegalArgumentException(
315: "Workflow.addTask(arg): arg must be a non-null Task");
316: }
317: subtasks.addElement(newTask);
318: // If the context of the new task is not set, set it to be the context of the parent task
319: if (newTask.getContext() == null) {
320: if (basetask != null) {
321: if (newTask instanceof NewTask) {
322: ((NewTask) newTask).setContext(basetask
323: .getContext());
324: }
325: }
326: }
327: changedSubtasks.clear();
328: clearTST();
329: }
330:
331: /** @param remTask Remove the specified Task from the Workflow's sub-task collection **/
332: public synchronized void removeTask(Task remTask) {
333: if (walkingSubtasks > 0) {
334: RuntimeException rt = new RuntimeException(
335: "Attempt to remove subtask while enum is active");
336: rt.printStackTrace();
337: }
338:
339: if (!subtasks.removeElement(remTask)) {
340: if (logger.isWarnEnabled()) {
341: logger.warn("removeTask not in Workflow: " + remTask,
342: new IllegalArgumentException());
343: }
344: }
345: changedSubtasks.remove(remTask.getUID());
346: clearTST();
347: }
348:
349: /** Note any previous values will be dropped.
350: * @param enumofConstraints setConstraints allows you to set the Enumeration
351: * of Constraints of a Workflow. */
352: public void setConstraints(Enumeration enumofConstraints) {
353: if (enumofConstraints == null) {
354: throw new IllegalArgumentException(
355: "Workflow.setConstraints(Enum e): illegal null argument");
356: }
357: constraints.removeAllElements();
358: while (enumofConstraints.hasMoreElements()) {
359: Object o = enumofConstraints.nextElement();
360: if (o instanceof Constraint) {
361: Constraint c = (Constraint) o;
362: if (checkConstraintAspects(c)) {
363: constraints.addElement(c);
364: } else {
365: throw new IllegalArgumentException(
366: "Workflow.setConstraints(): incompatible aspects");
367: }
368: } else {
369: //buzzzz... wrong answer - tried to pass in a null!
370: throw new IllegalArgumentException(
371: "Workflow.setConstraints(Enum e): all elements of e must be Constraints");
372: }
373: }
374: }
375:
376: private static boolean checkConstraintAspects(Constraint constraint) {
377: int constrainingAspectType = constraint.getConstrainingAspect();
378: int constrainedAspectType = constraint.getConstrainedAspect();
379: return !(constrainingAspectType != constrainedAspectType
380: && constrainingAspectType != AspectType.END_TIME
381: && constrainingAspectType != AspectType.START_TIME
382: && constrainedAspectType != AspectType.END_TIME && constrainedAspectType != AspectType.START_TIME);
383: }
384:
385: /** @param newConstraint addConstraint allows you to add a Constraint to a Workflow. */
386: public void addConstraint(Constraint newConstraint) {
387: if (newConstraint != null) {
388: if (!checkConstraintAspects(newConstraint)) {
389: throw new IllegalArgumentException(
390: "Workflow.addConstraint(): incompatible aspects");
391: }
392: constraints.addElement(newConstraint);
393: } else {
394: //buzzz... wrong answer - tried to pass in a null!
395: throw new IllegalArgumentException(
396: "Workflow.addConstraint(): illegal null argument");
397: }
398: }
399:
400: /** @param constraint the constraint to be removed. */
401: public void removeConstraint(Constraint constraint) {
402: if (constraint != null) {
403: constraints.removeElement(constraint);
404: } else {
405: //buzzz... wrong answer - tried to pass in a null!
406: throw new IllegalArgumentException(
407: "Workflow.removeConstraint(): illegal null argument");
408: }
409: }
410:
411: /**
412: * Returns first constraint for which the constraining event is
413: * defined and constrained event is undefined or violated with
414: * respect to constraining event.
415: **/
416:
417: public Constraint getNextPendingConstraint() {
418: for (int i = 0; i < constraints.size(); i++) {
419: Constraint c = (Constraint) constraints.elementAt(i);
420: if (isConstraintPendingOrViolated(c))
421: return c;
422: }
423: return null;
424: }
425:
426: /**
427: * @return true iff the constraint is violated or the constrained
428: * event is undefined.
429: */
430: public boolean isConstraintPendingOrViolated(Constraint c) {
431: ConstraintEvent ce1 = c.getConstrainingEventObject();
432: ConstraintEvent ce2 = c.getConstrainedEventObject();
433:
434: double constrainingValue = ce1.getValue();
435: if (Double.isNaN(constrainingValue))
436: return false;
437:
438: double constrainedValue = ce2.getValue();
439: if (Double.isNaN(constrainedValue))
440: return true;
441:
442: double diff = constrainedValue - constrainingValue
443: + c.getOffsetOfConstraint();
444: switch (c.getConstraintOrder()) {
445: case Constraint.BEFORE: // Same as LESSTHAN
446: if (diff <= 0.0)
447: return true;
448: break;
449: case Constraint.AFTER: // Same as GREATERTHAN
450: if (diff >= 0.0)
451: return true;
452: break;
453: case (Constraint.COINCIDENT): // Same as EQUALTO
454: if (diff == 0.0)
455: return true;
456: break;
457: }
458: return false; // Bogus constraint
459: }
460:
461: /**
462: * @return true iff the constrained event is defined and the
463: * constraint is violated.
464: */
465: public boolean isConstraintViolated(Constraint c) {
466: ConstraintEvent ce1 = c.getConstrainingEventObject();
467: ConstraintEvent ce2 = c.getConstrainedEventObject();
468:
469: double constrainingValue = ce1.getValue();
470: if (Double.isNaN(constrainingValue))
471: return false;
472:
473: double constrainedValue = ce2.getResultValue();
474: if (Double.isNaN(constrainedValue))
475: return false;
476:
477: double diff = constrainedValue - constrainingValue
478: + c.getOffsetOfConstraint();
479: switch (c.getConstraintOrder()) {
480: case Constraint.BEFORE: // Same as LESSTHAN
481: if (diff <= 0.0)
482: return true;
483: break;
484: case Constraint.AFTER: // Same as GREATERTHAN
485: if (diff >= 0.0)
486: return true;
487: break;
488: case (Constraint.COINCIDENT): // Same as EQUALTO
489: if (diff == 0.0)
490: return true;
491: break;
492: }
493: return false; // Bogus constraint
494: }
495:
496: /** sets a specific compute algorithm to use while computing the aggregated
497: * allocation results of the workflow. If this method is not called, the allocationresult
498: * will be aggregated using the default AllocationResultAggregator (Sum).
499: * @param aragg The AllocationResultAggregator to use.
500: * @see org.cougaar.planning.ldm.plan.AllocationResultAggregator
501: */
502: public void setAllocationResultAggregator(
503: AllocationResultAggregator aragg) {
504: currentARA = aragg;
505: }
506:
507: /** Return the Unique ID number for this object */
508: public UID getUID() {
509: return uid;
510: }
511:
512: public void setUID(UID u) {
513: if (uid != null)
514: throw new IllegalArgumentException("UID already set");
515: uid = u;
516: }
517:
518: public MessageAddress getOwner() {
519: return owner;
520: }
521:
522: /** serialize workflows by proxying the tasks all the tasks referred to
523: **/
524: private synchronized void writeObject(ObjectOutputStream stream)
525: throws IOException {
526: stream.defaultWriteObject();
527:
528: stream.writeObject(basetask);
529: stream.writeObject(subtasks);
530: stream.writeObject(constraints);
531: if (currentARA == AllocationResultAggregator.DEFAULT) {
532: stream.writeObject(null);
533: } else {
534: stream.writeObject(currentARA);
535: }
536: if (stream instanceof org.cougaar.core.persist.PersistenceOutputStream) {
537: stream.writeObject(myAnnotation);
538: // Probably superfluous since no plugin should be running, but
539: // logically required to match against the other accesses of
540: // this field.
541: synchronized (this ) {
542: stream.writeObject(changedSubtasks);
543: }
544: }
545: }
546:
547: private void readObject(ObjectInputStream stream)
548: throws ClassNotFoundException, IOException {
549: stream.defaultReadObject();
550:
551: basetask = (Task) stream.readObject();
552: subtasks = (Vector) stream.readObject();
553: constraints = (Vector) stream.readObject();
554: currentARA = (AllocationResultAggregator) stream.readObject();
555: if (currentARA == null) {
556: currentARA = AllocationResultAggregator.DEFAULT;
557: }
558: if (stream instanceof org.cougaar.core.persist.PersistenceInputStream) {
559: myAnnotation = (Annotation) stream.readObject();
560: changedSubtasks = (Set) stream.readObject();
561: } else {
562: changedSubtasks = new HashSet(3);
563: }
564: }
565:
566: // new property reading methods returned by WorkflowImplBeanInfo
567:
568: public String getParentTaskID() {
569: return getParentTask().getUID().toString();
570: }
571:
572: public synchronized String[] getTaskIDs() {
573: String taskID[] = new String[subtasks.size()];
574: for (int i = 0; i < subtasks.size(); i++)
575: taskID[i] = ((Task) subtasks.elementAt(i)).getUID()
576: .toString();
577: return taskID;
578: }
579:
580: public String getTaskID(int i) {
581: String taskID[] = getTaskIDs();
582: if (i < taskID.length)
583: return taskID[i];
584: else
585: return null;
586: }
587:
588: public Constraint[] getConstraintsAsArray() {
589: return (Constraint[]) constraints
590: .toArray(new Constraint[constraints.size()]);
591: }
592:
593: public synchronized AllocationResult getAllocationResult() {
594: return cachedar;
595: }
596:
597: // WARNING: STUBBED FOR NOW
598: public Constraint[] getViolatedConstraintsAsArray() {
599: Vector violations = getViolatedConstraintsVector(false);
600: if (violations == null)
601: return new Constraint[0];
602: return (Constraint[]) violations
603: .toArray(new Constraint[violations.size()]);
604: }
605:
606: private boolean _propagateP = true;
607:
608: public boolean isPropagatingToSubtasks() {
609: return _propagateP;
610: }
611:
612: public void setIsPropagatingToSubtasks(boolean isProp) {
613: _propagateP = isProp;
614: }
615:
616: /** @deprecated Use setIsPropagatingToSubtasks(boolean isProp) -defaults to true*/
617: public void setIsPropagatingToSubtasks() {
618: _propagateP = true;
619: }
620:
621: // used by ExpansionImpl for infrastructure propagating rescinds.
622: public synchronized List clearSubTasks() {
623: if (walkingSubtasks > 0) {
624: logger.error(
625: "Attempt to remove subtasks while enum is active",
626: new Throwable());
627: }
628:
629: ArrayList l = new ArrayList(subtasks);
630: subtasks.removeAllElements();
631: changedSubtasks.clear();
632: return l;
633: }
634:
635: private transient Annotation myAnnotation = null;
636:
637: public void setAnnotation(Annotation pluginAnnotation) {
638: myAnnotation = pluginAnnotation;
639: }
640:
641: public Annotation getAnnotation() {
642: return myAnnotation;
643: }
644:
645: }
|