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.plugin.deletion;
028:
029: import java.util.ArrayList;
030: import java.util.Arrays;
031: import java.util.Collection;
032: import java.util.Date;
033: import java.util.Enumeration;
034: import java.util.Iterator;
035: import java.util.List;
036: import java.util.Set;
037:
038: import org.cougaar.core.mts.MessageAddress;
039: import org.cougaar.core.plugin.deletion.DeletionPlugin;
040: import org.cougaar.core.plugin.deletion.DeletionPolicy;
041: import org.cougaar.core.util.UID;
042: import org.cougaar.planning.ldm.asset.Asset;
043: import org.cougaar.planning.ldm.asset.ClusterPG;
044: import org.cougaar.planning.ldm.plan.Allocation;
045: import org.cougaar.planning.ldm.plan.AllocationforCollections;
046: import org.cougaar.planning.ldm.plan.AllocationResult;
047: import org.cougaar.planning.ldm.plan.Constraint;
048: import org.cougaar.planning.ldm.plan.Expansion;
049: import org.cougaar.planning.ldm.plan.MPTask;
050: import org.cougaar.planning.ldm.plan.NewConstraint;
051: import org.cougaar.planning.ldm.plan.NewTask;
052: import org.cougaar.planning.ldm.plan.NewWorkflow;
053: import org.cougaar.planning.ldm.plan.PlanElement;
054: import org.cougaar.planning.ldm.plan.PlanElementSet;
055: import org.cougaar.planning.ldm.plan.Task;
056: import org.cougaar.planning.plugin.util.PluginHelper;
057: import org.cougaar.util.Filters;
058: import org.cougaar.util.UnaryPredicate;
059:
060: /**
061: * DeletionPlugin provides generic deletion services to a agent.
062: * These consist of:
063: *
064: * Identification of deletable Allocations to non-org assets
065: * Identification of tasks having deletable dispositions (PlanElements)
066: * Removal of deletable subtasks from Expansions
067: * Identification of Aggregations to deletable tasks
068: **/
069:
070: public class TaskDeletionPlugin extends DeletionPlugin {
071: private UnaryPredicate deletablePlanElementsPredicate;
072:
073: private class DeletablePlanElementsPredicate implements
074: UnaryPredicate {
075: public boolean execute(Object o) {
076: if (o instanceof PlanElement) {
077: PlanElement pe = (PlanElement) o;
078: if (isTimeToDelete(pe)) {
079: if (pe instanceof Allocation) {
080: AllocationforCollections alloc = (AllocationforCollections) pe;
081: Asset asset = alloc.getAsset();
082: ClusterPG cpg = asset.getClusterPG();
083: if (cpg == null)
084: return true; // Can't be remote w/o ClusterPG
085: MessageAddress destination = cpg
086: .getMessageAddress();
087: if (destination == null) {
088: return true; // Can't be remote w null destination
089: }
090: UID remoteUID = alloc.getAllocationTaskUID();
091: boolean remoteIsDeleted = alloc
092: .isAllocationTaskDeleted();
093: return remoteUID == null || remoteIsDeleted;
094: // Can delete if remote task is deleted or non-existent
095: }
096: if (pe instanceof Expansion) {
097: Expansion exp = (Expansion) pe;
098: return !(exp.getWorkflow().getTasks()
099: .hasMoreElements());
100: }
101: return false;
102: }
103: }
104: return false;
105: }
106: }
107:
108: private List deletablePlanElementFilter(
109: UnaryPredicate deletablePred, Collection planElements) {
110: return new ArrayList(Filters
111: .filter(planElements, deletablePred));
112: }
113:
114: /**
115: * Setup subscriptions. We maintain no standing subscriptions, but
116: * we do have parameters to initialize -- the period between
117: * deletion activities and the deletion time margin.
118: **/
119: protected void setupSubscriptions() {
120: super .setupSubscriptions();
121: deletablePlanElementsPredicate = new DeletablePlanElementsPredicate();
122: }
123:
124: private static final UnaryPredicate planElementPredicate = new UnaryPredicate() {
125: public boolean execute(Object o) {
126: return o instanceof PlanElement;
127: }
128: };
129:
130: private class PESet {
131: private PlanElementSet planElementSet;
132:
133: public PlanElement findPlanElement(UID uid) {
134: if (planElementSet == null) {
135: queryBlackBoard();
136: }
137: return planElementSet.findPlanElement(uid);
138: }
139:
140: private void queryBlackBoard() {
141: Collection planElements = getBlackboardService().query(
142: planElementPredicate);
143: planElementSet = new PlanElementSet();
144: planElementSet.addAll(planElements);
145: }
146:
147: public Collection toCollection() {
148: List planElementList;
149: if (planElementSet == null) {
150: queryBlackBoard();
151: }
152: if (planElementSet != null) {
153: planElementList = Arrays.asList(planElementSet
154: .toArray());
155: } else {
156: planElementList = new ArrayList();
157: }
158: return planElementList;
159: }
160:
161: public void clear() {
162: planElementSet = null;
163: }
164: }
165:
166: private Set getDeletablePlanElements() {
167: if (deletablePlanElements == null) {
168: deletablePlanElements = new PlanElementSet();
169: Collection c = deletablePlanElementFilter(
170: deletablePlanElementsPredicate, peSet
171: .toCollection());
172: if (c.size() > 0) {
173: if (logger.isDebugEnabled())
174: logger.debug("Found " + c.size()
175: + " deletable PlanElements");
176: deletablePlanElements.addAll(c);
177: }
178: }
179: return deletablePlanElements;
180: }
181:
182: private PESet peSet = new PESet();
183: private PlanElementSet deletablePlanElements = null;
184: protected int wakeCount = 0;
185: private int numDeletedTasks = 0;
186:
187: /**
188: * Called from execute when the alarm expires.
189: *
190: * The procedure is:
191: * Find new allocations for tasks that deletable and mark the allocations
192: * Find tasks with deletable dispositions and mark them
193: * Find deletable tasks that are subtasks of an expansion and
194: * remove them from the expansion and remove them from the
195: * logplan.
196: **/
197: protected void checkDeletables() {
198: numDeletedTasks = 0;
199: checkDeletablePlanElements();
200: if (logger.isDebugEnabled()) {
201: if (++wakeCount > 4) {
202: wakeCount = 0;
203: printAllPEs();
204: }
205: }
206: peSet.clear();
207: deletablePlanElements = null;
208: super .checkDeletables();
209: if (logger.isInfoEnabled()) {
210: if (numDeletedTasks > 0) {
211: logger.info("," + getAgentIdentifier() + ","
212: + new Date(currentTimeMillis()) + ","
213: + numDeletedTasks
214: + ", tasks deleted this cycle");
215: }
216: }
217: }
218:
219: /**
220: * Check all plan elements that are superficially deletable (as
221: * determined by the deletable plan elements predicate). Starting
222: * from each such plan element, we work backward, toward the root
223: * tasks, looking for a deletable tasks. All the methods beginning
224: * with "check" work back toward the roots. The methods beginning
225: * with "delete" actually delete the objects.
226: **/
227: private void checkDeletablePlanElements() {
228: Set s = getDeletablePlanElements();
229: for (Iterator i = s.iterator(); i.hasNext();) {
230: checkPlanElement((PlanElement) i.next());
231: }
232: }
233:
234: private void printAllPEs() {
235: Collection c = blackboard.query(new UnaryPredicate() {
236: public boolean execute(Object o) {
237: return o instanceof PlanElement;
238: }
239: });
240: if (!c.isEmpty()) {
241: logger.debug("Undeletable Tasks");
242: for (Iterator i = c.iterator(); i.hasNext();) {
243: PlanElement pe = (PlanElement) i.next();
244: String reason = canDelete(pe);
245: if (reason == null) {
246: logger.debug(pe.getTask().getUID() + " "
247: + pe.getTask().getVerb() + ": Deletable");
248: } else {
249: logger.debug(pe.getTask().getUID() + " "
250: + pe.getTask().getVerb() + ": " + reason);
251: }
252: }
253: }
254: }
255:
256: private String canDelete(PlanElement pe) {
257: if (!isTimeToDelete(pe))
258: return "Not time to delete";
259: if (pe instanceof Allocation) {
260: AllocationforCollections alloc = (AllocationforCollections) pe;
261: Asset asset = alloc.getAsset();
262: ClusterPG cpg = asset.getClusterPG();
263: if (cpg != null) {
264: MessageAddress destination = cpg.getMessageAddress();
265: if (destination != null) {
266: if (alloc.getAllocationTaskUID() == null) {
267: return "Awaiting remote task creation";
268: }
269: if (!alloc.isAllocationTaskDeleted()) {
270: return "Remote task not deleted";
271: }
272: }
273: }
274: }
275: if (pe instanceof Expansion) {
276: Expansion exp = (Expansion) pe;
277: if (exp.getWorkflow().getTasks().hasMoreElements()) {
278: return "Expands to non-empty workflow";
279: }
280: }
281: Task task = pe.getTask();
282: if (task instanceof MPTask) {
283: MPTask mpTask = (MPTask) task;
284: for (Enumeration e = mpTask.getParentTasks(); e
285: .hasMoreElements();) {
286: Task parent = (Task) e.nextElement();
287: PlanElement ppe = parent.getPlanElement();
288: // This is always an Aggregation
289: String parentReason = canDelete(ppe);
290: if (parentReason != null)
291: return "Has undeletable parent: " + parentReason;
292: }
293: } else {
294: UID ptuid = task.getParentTaskUID();
295: if (ptuid != null) {
296: PlanElement ppe = peSet.findPlanElement(ptuid);
297: if (ppe != null) {
298: if (!(ppe instanceof Expansion)) {
299: String parentReason = canDelete(ppe);
300: if (parentReason != null)
301: return "Has undeletable parent: "
302: + parentReason;
303: }
304: }
305: }
306: }
307: return null;
308: }
309:
310: /**
311: * Check one plan element. The plan is already superficially
312: * deletable, but cannot actually be deleted unless its task
313: * is deletable.
314: **/
315: private void checkPlanElement(PlanElement pe) {
316: if (isDeleteAllowed(pe)) {
317: delete(pe.getTask());
318: }
319: }
320:
321: /**
322: * A plan element is ready to delete if is time to delete the plan
323: * element and if its task can be deleted without messing things
324: * up. Its task can be deleted if it has no parent, is a subtask
325: * of an expansion, or if its parent can be deleted.
326: **/
327: private boolean isDeleteAllowed(PlanElement pe) {
328: if (pe == null)
329: return true; // Hmmmmm, can this happen?
330: Task task = pe.getTask();
331: if (task instanceof MPTask) {
332: MPTask mpTask = (MPTask) task;
333: for (Enumeration e = mpTask.getParentTasks(); e
334: .hasMoreElements();) {
335: Task parent = (Task) e.nextElement();
336: PlanElement ppe = parent.getPlanElement();
337: // This is always an Aggregation
338: if (!isDeleteAllowed(ppe))
339: return false;
340: }
341: return true;
342: } else {
343: UID ptuid = task.getParentTaskUID();
344: if (ptuid == null)
345: return true; // Can always delete a root task
346: PlanElement ppe = peSet.findPlanElement(ptuid);
347: if (ppe == null) { // Parent is in another agent
348: return true; // It's ok to delete it
349: }
350: if (ppe instanceof Expansion) {
351: return true; // Can always delete a subtask
352: } else {
353: // Otherwise, can only delete if the pe can be deleted
354: return getDeletablePlanElements().contains(ppe)
355: && isDeleteAllowed(ppe);
356: }
357: }
358: }
359:
360: // private void delete(PlanElement pe) {
361: // delete(pe.getTask());
362: // }
363:
364: private void delete(Task task) {
365: if (logger.isDebugEnabled())
366: logger.debug("Deleting " + task);
367: ((NewTask) task).setDeleted(true); // Prevent LP from propagating deletion
368: if (task instanceof MPTask) {
369: // Delete multiple parent tasks
370: MPTask mpTask = (MPTask) task;
371: if (logger.isDebugEnabled())
372: logger.debug("Task is MPTask, deleting parents");
373: for (Enumeration e = mpTask.getParentTasks(); e
374: .hasMoreElements();) {
375: Task parent = (Task) e.nextElement();
376: delete(parent); // ppe is always an Aggregation
377: }
378: if (logger.isDebugEnabled())
379: logger.debug("All parents deleted");
380: } else {
381: if (logger.isDebugEnabled())
382: logger.debug("Checking parent");
383: UID ptuid = task.getParentTaskUID();
384: if (ptuid == null) {
385: if (logger.isDebugEnabled())
386: logger.debug("Deleting root " + task.getUID());
387: deleteRootTask(task);
388: numDeletedTasks++;
389: } else {
390: PlanElement ppe = peSet.findPlanElement(ptuid);
391: if (ppe == null) { // Parent is in another agent
392: // Delete the task
393: if (logger.isDebugEnabled())
394: logger.debug("Parent " + ptuid
395: + " is remote, deleting task"
396: + task.getUID());
397: deleteReceivedTask(task);
398: numDeletedTasks++;
399: } else {
400: if (ppe instanceof Expansion) {
401: if (logger.isDebugEnabled())
402: logger.debug("Parent is expansion of "
403: + ptuid + ", deleting subtask "
404: + task.getUID());
405: deleteSubtask((Expansion) ppe, task);
406: numDeletedTasks++;
407: } else {
408: if (logger.isDebugEnabled())
409: logger
410: .debug("Parent is other, propagating");
411: delete(ppe.getTask());
412: // Not sure this is possible, but parallels "isDeleteAllowed"
413: }
414: }
415: }
416: }
417: }
418:
419: private void deleteRootTask(Task task) {
420: blackboard.publishRemove(task);
421: }
422:
423: private void deleteReceivedTask(Task task) {
424: blackboard.publishRemove(task);
425: }
426:
427: /**
428: * Delete a subtask of an expansion. Find all constraints where
429: * the subtask is the constraining task and replace the constraint
430: * with an absolute constraint against the constraining value.
431: **/
432: private void deleteSubtask(Expansion exp, Task subtask) {
433: NewWorkflow wf = (NewWorkflow) exp.getWorkflow();
434: List constraintsToRemove = new ArrayList();
435: for (Enumeration e = wf.getTaskConstraints(subtask); e
436: .hasMoreElements();) {
437: NewConstraint constraint = (NewConstraint) e.nextElement();
438: if (constraint.getConstrainingTask() == subtask) {
439: double value = constraint
440: .computeValidConstrainedValue();
441: constraint.setConstrainingTask(null);
442: constraint.setAbsoluteConstrainingValue(value);
443: } else if (constraint.getConstrainedTask() == subtask) {
444: constraintsToRemove.add(constraint);
445: }
446: }
447: wf.removeTask(subtask);
448: for (Iterator i = constraintsToRemove.iterator(); i.hasNext();) {
449: wf.removeConstraint((Constraint) i.next());
450: }
451: if (!wf.getTasks().hasMoreElements() && isTimeToDelete(exp)) {
452: checkPlanElement(exp); // Ready to be deleted.
453: }
454: if (logger.isDebugEnabled())
455: logger.debug("Deleting subtask " + subtask);
456: blackboard.publishRemove(subtask);
457: }
458:
459: private boolean isTimeToDelete(PlanElement pe) {
460: long et = computeExpirationTime(pe);
461: // if (logger.isDebugEnabled()) logger.debug("Expiration time is " + new java.util.Date(et));
462: boolean result = et == 0L || et < scenarioNow;
463: // if (result) {
464: // if (logger.isDebugEnabled()) logger.debug("isTimeToDelete: " + new java.util.Date(et));
465: // }
466: return result;
467: }
468:
469: private long computeExpirationTime(PlanElement pe) {
470: double et;
471: Task task = pe.getTask();
472: AllocationResult ar = pe.getReportedResult();
473: if (ar == null) {
474: ar = pe.getEstimatedResult();
475: }
476: et = PluginHelper.getEndTime(ar);
477: if (Double.isNaN(et))
478: try {
479: et = PluginHelper.getEndTime(task);
480: } catch (RuntimeException re) {
481: et = Double.NaN;
482: }
483: if (Double.isNaN(et))
484: et = PluginHelper.getStartTime(ar);
485: if (Double.isNaN(et))
486: try {
487: et = PluginHelper.getStartTime(task);
488: } catch (RuntimeException re) {
489: et = Double.NaN;
490: }
491: if (Double.isNaN(et))
492: return Long.MAX_VALUE; //return 0L;
493: for (Iterator i = deletionPolicies.iterator(); i.hasNext();) {
494: DeletionPolicy policy = (DeletionPolicy) i.next();
495: if (policy.getPredicate().execute(task)) {
496: return ((long) et) + policy.getDeletionDelay();
497: }
498: }
499: return 0L;
500: // Should not get here; DefaultDeletionPolicy should always apply
501: }
502: }
|