001: /*
002: * <copyright>
003: *
004: * Copyright 1997-2004 TASC
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: package org.cougaar.glm.plugins.multiplesuppliers;
027:
028: import java.util.Enumeration;
029: import java.util.Vector;
030:
031: import org.cougaar.core.blackboard.IncrementalSubscription;
032: import org.cougaar.core.service.LoggingService;
033: import org.cougaar.glm.ldm.Constants;
034: import org.cougaar.glm.ldm.asset.Organization;
035: import org.cougaar.lib.util.UTILExpand;
036: import org.cougaar.lib.util.UTILPreference;
037: import org.cougaar.lib.util.UTILPrepPhrase;
038: import org.cougaar.planning.ldm.plan.Allocation;
039: import org.cougaar.planning.ldm.plan.AllocationResult;
040: import org.cougaar.planning.ldm.plan.AspectType;
041: import org.cougaar.planning.ldm.plan.AspectValue;
042: import org.cougaar.planning.ldm.plan.Expansion;
043: import org.cougaar.planning.ldm.plan.NewPrepositionalPhrase;
044: import org.cougaar.planning.ldm.plan.NewTask;
045: import org.cougaar.planning.ldm.plan.NewWorkflow;
046: import org.cougaar.planning.ldm.plan.PlanElement;
047: import org.cougaar.planning.ldm.plan.Preference;
048: import org.cougaar.planning.ldm.plan.PrepositionalPhrase;
049: import org.cougaar.planning.ldm.plan.ScoringFunction;
050: import org.cougaar.planning.ldm.plan.Task;
051: import org.cougaar.planning.ldm.plan.Verb;
052: import org.cougaar.planning.ldm.plan.Workflow;
053: import org.cougaar.planning.plugin.legacy.SimplePlugin;
054: import org.cougaar.util.UnaryPredicate;
055:
056: /**
057: * The Base SourceExpander class.
058: */
059: public class SourceExpander extends SimplePlugin {
060: // The implemented approach for SOURCE logic requires all suppliers to have the SOURCE plugins.
061: // An alternate approach sends SUPPLY tasks to clusters that don't have MultipleSupplierCapable role.
062: // The downside of this approach is that all SUPPLY plugins must ignore SUPPLY tasks whose parent task
063: // is a SOURCE task. In this alternate approach, SOURCE-capable clusters must have a role of MultipleSupplierCapable.
064:
065: private IncrementalSubscription inputTasks = null;
066: private IncrementalSubscription expansions = null;
067: private IncrementalSubscription orgAssets = null;
068: private String clusterName = null;
069:
070: static class InputTasksP implements UnaryPredicate {
071: public boolean execute(Object o) {
072: if (o instanceof Task) {
073: Task task = (Task) o;
074: if (task.getWorkflow() == null) {
075: return true;
076: }
077: }
078: return false;
079: }
080: }
081:
082: static class ExpansionsP implements UnaryPredicate {
083: public boolean execute(Object o) {
084: if (o instanceof Expansion) {
085: return true;
086: }
087: return false;
088: }
089: }
090:
091: static class OrganizationAssetsP implements UnaryPredicate {
092: public boolean execute(Object o) {
093: if (o instanceof Organization)
094: return true;
095: return false;
096: }
097: }
098:
099: /**
100: * rely upon load-time introspection to set these services -
101: * don't worry about revokation.
102: */
103: public final void setLoggingService(LoggingService bs) {
104: logger = bs;
105:
106: prepHelper = new UTILPrepPhrase(logger);
107: prefHelper = new UTILPreference(logger);
108: expandHelper = new UTILExpand(logger);
109: }
110:
111: /**
112: * Get the logging service, for subclass use.
113: */
114: protected LoggingService getLoggingService() {
115: return logger;
116: }
117:
118: /**
119: * This method is meant to be overridden by subclasses for those instances where
120: * the subclass needs to do any initialization at the beginning of each execute() loop.
121: * The default implementation does nothing.
122: */
123: protected void transactInit() {
124: }
125:
126: /**
127: * This method is meant to be overridden by subclasses for those instances where
128: * the sublass needs to do processing when a task change is detected.
129: * The defualt implementation of this method does nothing.
130: * @param task a Task that has marked as changed.
131: */
132: protected void handleChangedTask(Task task) {
133: }
134:
135: /**
136: * Sublass implementations should override this method to provide the Vector of Organizations
137: * that should be used as suppliers for this task.
138: *
139: * @param task the task that suppliers are needed for.
140: * @return (ordered) list of Organizations that can serve as supplier for the input Task.
141: */
142: protected Vector getSuppliersList(Task task) {
143: Vector returnVector = new Vector();
144: // default to SELF as only supplier
145: returnVector.add(Utility.findNamedOrganization(
146: getClusterName(), getOrganizationAssets()));
147: return returnVector;
148: }
149:
150: /**
151: * Get next supplier from a list of suppliers in the WITHSUPPLIERS phrase of a task.
152: *
153: * @param task the Task that contains a list of suppliers in a WITHSUPPLIERS phrase.
154: * @param curSupplier the last supplier that was used.
155: * @return the next supplier to use
156: */
157: protected Organization getNextSupplier(Task task,
158: Organization curSupplier) {
159: Organization nextSupplier = null;
160:
161: String curSupplierName = curSupplier.getItemIdentificationPG()
162: .getNomenclature();
163:
164: PrepositionalPhrase pPhrase = prepHelper.getPrepNamed(task,
165: Grammar.WITHSUPPLIERS);
166: Vector suppliers = (Vector) pPhrase.getIndirectObject();
167: Enumeration suppliersEnum = suppliers.elements();
168:
169: // Start by getting past the curSupplier in the supplierList.
170: // If curSupplier is null, start at the beginning of the supplierList.
171: if (curSupplier != null) {
172: while (suppliersEnum.hasMoreElements()) {
173: Organization supplier = (Organization) suppliersEnum
174: .nextElement();
175: String tmpSupplierName = supplier
176: .getItemIdentificationPG().getNomenclature();
177: if (tmpSupplierName.equals(curSupplierName)) {
178: // Found curSupplier in list, the nextElement will be the next supplier.
179: break;
180: }
181: }
182: }
183:
184: if (suppliersEnum.hasMoreElements()) {
185: nextSupplier = (Organization) suppliersEnum.nextElement();
186: }
187:
188: return nextSupplier;
189: }
190:
191: protected String getClusterName() {
192: return clusterName;
193: }
194:
195: public boolean getOrganizationAssetsHasChanged() {
196: return orgAssets.hasChanged();
197: }
198:
199: public Enumeration getOrganizationAssets() {
200: return orgAssets.elements();
201: }
202:
203: public Enumeration getOrganizationAssetsAdded() {
204: return orgAssets.getAddedList();
205: }
206:
207: public Enumeration getOrganizationAssetsChanged() {
208: return orgAssets.getChangedList();
209: }
210:
211: public Enumeration getOrganizationAssetsRemoved() {
212: return orgAssets.getRemovedList();
213: }
214:
215: public Enumeration getInputTasks() {
216: return inputTasks.elements();
217: }
218:
219: /**
220: * Standard SimplePlugin method.
221: * Store cluster name and setup subscriptions for input Tasks, Expansions and Allocations.
222: */
223: protected void setupSubscriptions() {
224: clusterName = getMessageAddress().getAddress();
225:
226: orgAssets = (IncrementalSubscription) subscribe(new OrganizationAssetsP());
227: inputTasks = (IncrementalSubscription) subscribe(new InputTasksP());
228: expansions = (IncrementalSubscription) subscribe(new ExpansionsP());
229: }
230:
231: public synchronized void execute() {
232: // Call transactionInit() first.
233: // This way subclasses can do initialization at the beginning of each execute() loop.
234: transactInit();
235:
236: if (inputTasks != null && inputTasks.hasChanged()) {
237: Enumeration addedInputTasksEnum = inputTasks.getAddedList();
238: while (addedInputTasksEnum.hasMoreElements()) {
239: Task tmpTask = (Task) addedInputTasksEnum.nextElement();
240: expandTask(tmpTask);
241: }
242: Enumeration changedInputTasksEnum = inputTasks
243: .getChangedList();
244: while (changedInputTasksEnum.hasMoreElements()) {
245: Task task = (Task) changedInputTasksEnum.nextElement();
246: handleChangedTask(task);
247: }
248: }
249:
250: if (expansions != null && expansions.hasChanged()) {
251: Enumeration changedExpansions = expansions.getChangedList();
252: while (changedExpansions.hasMoreElements()) {
253: Expansion expansion = (Expansion) changedExpansions
254: .nextElement();
255: expanderRollup(expansion);
256: }
257:
258: Enumeration removedExpansions = expansions.getRemovedList();
259: while (removedExpansions.hasMoreElements()) {
260: Expansion expansion = (Expansion) removedExpansions
261: .nextElement();
262: Workflow workflow = expansion.getWorkflow();
263: if (workflow != null) {
264: Enumeration subtasks = workflow.getTasks();
265: if (subtasks != null) {
266: while (subtasks.hasMoreElements()) {
267: publishRemove(subtasks.nextElement());
268: }
269: }
270: }
271: }
272: }
273: }
274:
275: /**
276: * This method provides the fundamental logic for the Multiple Suppliers plugins.
277: * This method is called by the plugin's execute() method when a Source Task's Expansion
278: * is marked as changed. This method will detect if the SOURCE task is completely satisfied.
279: * If not, the next supplier will be identified and that Organization will be sent a task
280: * to help fulfill the SOURCE request. When the SOURCE task is completely satisfied or when
281: * there are no more suppliers to use, this method will rollup the results and notify the original
282: * requester.
283: *
284: * @param expansion the Expansion object that has been marked as changed.
285: */
286: protected synchronized void expanderRollup(Expansion expansion) {
287: Task task = expansion.getTask();
288: String verbStr = task.getVerb().toString();
289: if (verbStr.equals(Grammar.SOURCE)) {
290: Workflow workflow = expansion.getWorkflow();
291: if (workflow != null) {
292: AllocationResult reportedResult = expansion
293: .getReportedResult();
294: if (reportedResult != null) {
295: AllocationResult estimatedResult = expansion
296: .getEstimatedResult();
297: if (estimatedResult == null
298: || !Utility.equalResults(estimatedResult,
299: reportedResult)) {
300:
301: double prefQuantity = task
302: .getPreferredValue(AspectType.QUANTITY);
303: double reportedQuantity = reportedResult
304: .getValue(AspectType.QUANTITY);
305: double shortfallCount = prefQuantity
306: - reportedQuantity;
307:
308: if (shortfallCount == 0) {
309: // At the point where shortfallCount is 0,
310: // make sure there are no unnecessary subtasks at the end of the workflow.
311: boolean workflowChanged = false;
312: boolean deletedFromEndOfWorkflow = true;
313: while (deletedFromEndOfWorkflow) {
314: deletedFromEndOfWorkflow = false;
315: Task lastSubtask = Utility
316: .getLastSubtaskInWorkflow(workflow);
317: Allocation allocation = (Allocation) lastSubtask
318: .getPlanElement();
319: AllocationResult lastSubtaskResult = allocation
320: .getReportedResult();
321: if (lastSubtaskResult
322: .getValue(AspectType.QUANTITY) == 0) {
323: workflowChanged = true;
324: deletedFromEndOfWorkflow = true;
325: ((NewWorkflow) workflow)
326: .removeTask(lastSubtask);
327: publishRemove(lastSubtask);
328: }
329: }
330: if (workflowChanged) {
331: publishChange(workflow);
332: }
333:
334: // Create Estimated Results. Setup default values first.
335: double estimatedCost = 0.0;
336: double estimatedQuantity = reportedQuantity;
337: double estimatedStart = task
338: .getPreferredValue(AspectType.START_TIME);
339: double estimatedEnd = task
340: .getPreferredValue(AspectType.END_TIME);
341: // Override default values with those in Reported Result if exist there.
342: if (reportedResult
343: .isDefined(AspectType.COST)) {
344: estimatedCost = reportedResult
345: .getValue(AspectType.COST);
346: }
347: if (reportedResult
348: .isDefined(AspectType.START_TIME)) {
349: estimatedStart = reportedResult
350: .getValue(AspectType.START_TIME);
351: }
352: if (reportedResult
353: .isDefined(AspectType.END_TIME)) {
354: estimatedEnd = reportedResult
355: .getValue(AspectType.END_TIME);
356: }
357:
358: AllocationResult newEstimatedResult = Utility
359: .createAllocationResult(
360: getFactory(), true,
361: estimatedStart,
362: estimatedEnd,
363: estimatedCost,
364: estimatedQuantity);
365: expansion
366: .setEstimatedResult(newEstimatedResult);
367: publishChange(expansion);
368: } else {
369: Enumeration subtasks = workflow.getTasks();
370: Task lastSubtask = Utility
371: .getLastSubtaskInWorkflow(workflow);
372:
373: if (shortfallCount < 0) {
374: // A negative shortfall means we have too many items.
375: // Remove the last subtask. We'll rework that one from scratch when this wakes back up.
376: ((NewWorkflow) workflow)
377: .removeTask(lastSubtask);
378: publishRemove(lastSubtask);
379: publishChange(workflow);
380: publishChange(expansion);
381: } else {
382: PlanElement planElement = lastSubtask
383: .getPlanElement();
384: if (!(planElement instanceof Allocation)) {
385: expansion
386: .setEstimatedResult(reportedResult);
387: publishChange(expansion);
388: } else {
389: Allocation allocation = (Allocation) lastSubtask
390: .getPlanElement();
391:
392: // Need to determine if there is a "next" supplier.
393: // Two cases:
394: // 1. If this is the initial source mode, then the last supplier used was unable
395: // to completely fill the request. So, look for the next supplier in the suppliers list.
396: // 2. If one of the already used suppliers has retracted one of the allocations, then the
397: // lastsupplier we used may still have more. Check him first if he did not previously
398: // report a shortfall.
399: double lastSubtaskPrefQuantity = lastSubtask
400: .getPreferredValue(AspectType.QUANTITY);
401: AllocationResult lastSubtaskResult = allocation
402: .getReportedResult();
403: double lastSubtaskReportedQuantity = lastSubtaskResult
404: .getValue(AspectType.QUANTITY);
405: Organization lastSubtaskSupplier = (Organization) allocation
406: .getAsset();
407:
408: Organization nextSupplier = (Organization) allocation
409: .getAsset();
410: if (lastSubtaskReportedQuantity < lastSubtaskPrefQuantity) {
411: nextSupplier = getNextSupplier(
412: lastSubtask,
413: lastSubtaskSupplier);
414: }
415:
416: if (nextSupplier != null) {
417: NewTask subtask = Utility
418: .cloneTask(
419: getFactory(),
420: lastSubtask,
421: null);
422:
423: AspectValue quantityAV = AspectValue
424: .newAspectValue(
425: AspectType.QUANTITY,
426: shortfallCount);
427: ScoringFunction quantitySF = ScoringFunction
428: .createNearOrBelow(
429: quantityAV,
430: shortfallCount);
431: Preference quantityPreference = getFactory()
432: .newPreference(
433: AspectType.QUANTITY,
434: quantitySF);
435: prefHelper.replacePreference(
436: subtask,
437: quantityPreference);
438:
439: NewPrepositionalPhrase newPhrase = getFactory()
440: .newPrepositionalPhrase();
441: newPhrase
442: .setPreposition(Grammar.USESUPPLIER);
443: newPhrase
444: .setIndirectObject(nextSupplier);
445: prepHelper.replacePrepOnTask(
446: subtask, newPhrase);
447:
448: boolean redirectToCluster = false;
449: // If nextSupplier is myself, set the verb to SUPPLY.
450: // If nextSupplier does not have role of MultipleSupplierCapable, set the verb to SUPPLY.
451: // Otherwise, set the verb to SOURCE.
452: if (nextSupplier == Utility
453: .findNamedOrganization(
454: getClusterName(),
455: getOrganizationAssets())) {
456: subtask
457: .setVerb(Verb
458: .get(Constants.Verb.SUPPLY));
459: } else {
460: subtask
461: .setVerb(Verb
462: .get(Grammar.SOURCE));
463: }
464:
465: ((NewWorkflow) workflow)
466: .addTask(subtask);
467: subtask.setWorkflow(workflow);
468: publishChange(workflow);
469: publishAdd(subtask);
470: } else {
471: expansion
472: .setEstimatedResult(reportedResult);
473: publishChange(expansion);
474: }
475: }
476: }
477: }
478: }
479: }
480: }
481: }
482: }
483:
484: /**
485: * Create the original subtask to be sent to the first supplier in order to fill a SOURCE request.
486: *
487: * @param task the SOURCE task that needs to be expanded.
488: */
489: protected boolean expandTask(Task task) {
490: boolean retval = false;
491: NewTask subtask = null;
492: Vector subtasks = new Vector();
493:
494: String verbStr = task.getVerb().toString();
495: if (verbStr.equals(Grammar.SOURCE)) {
496: retval = true;
497:
498: Vector mySuppliers = getSuppliersList(task);
499: if (mySuppliers == null) {
500: return retval;
501: }
502:
503: subtask = Utility.createSubtaskFromTask(getFactory(), task);
504:
505: Vector usedSuppliersVector = null;
506: // Remove any "already used" suppliers from the mySuppliers list, except for SELF.
507: // Then add the remaining "new" suppliers to the UsedSuppliersList (if they aren't already in there)
508: // so they won't get tasked by other suppliers' source tasks
509: NewPrepositionalPhrase usedSuppliersPhrase = (NewPrepositionalPhrase) prepHelper
510: .getPrepNamed(subtask, Grammar.WITHUSEDSUPPLIERS);
511: if (usedSuppliersPhrase != null) {
512: usedSuppliersVector = (Vector) usedSuppliersPhrase
513: .getIndirectObject();
514: Enumeration usedSuppliersEnum = usedSuppliersVector
515: .elements();
516: while (usedSuppliersEnum.hasMoreElements()) {
517: String usedSupplierName = (String) usedSuppliersEnum
518: .nextElement();
519: Enumeration mySupplierEnum = mySuppliers.elements();
520: while (mySupplierEnum.hasMoreElements()) {
521: Organization mySupplier = (Organization) mySupplierEnum
522: .nextElement();
523: String mySupplierName = mySupplier
524: .getItemIdentificationPG()
525: .getNomenclature();
526: if (mySupplierName.equals(usedSupplierName)
527: && !mySupplierName
528: .equals(getClusterName())) {
529: mySuppliers.removeElement(mySupplier);
530: break;
531: }
532: }
533: }
534: } else {
535: usedSuppliersPhrase = getFactory()
536: .newPrepositionalPhrase();
537: usedSuppliersPhrase
538: .setPreposition(Grammar.WITHUSEDSUPPLIERS);
539: usedSuppliersPhrase.setIndirectObject(new Vector());
540: prepHelper.replacePrepOnTask(subtask,
541: usedSuppliersPhrase);
542: usedSuppliersVector = (Vector) usedSuppliersPhrase
543: .getIndirectObject();
544: }
545:
546: if (!usedSuppliersVector.contains(getClusterName())) {
547: usedSuppliersVector.add(getClusterName());
548: }
549: Enumeration suppliersEnum = mySuppliers.elements();
550: while (suppliersEnum.hasMoreElements()) {
551: Organization supplier = (Organization) suppliersEnum
552: .nextElement();
553: String supplierName = supplier
554: .getItemIdentificationPG().getNomenclature();
555: if (!usedSuppliersVector.contains(supplierName)) {
556: usedSuppliersVector.add(supplierName);
557: }
558: }
559:
560: NewPrepositionalPhrase newPhrase = getFactory()
561: .newPrepositionalPhrase();
562: newPhrase.setPreposition(Grammar.WITHSUPPLIERS);
563: newPhrase.setIndirectObject(mySuppliers);
564: prepHelper.replacePrepOnTask(subtask, newPhrase);
565:
566: // Put in a USESUPPLIER clause in order to allocate this subtask to the first supplier.
567: // Note that the SourceAllocator will create a FailedAllocation if the indirect object
568: // of the USESUPPLIER phrase is null.
569: Organization firstSupplier = null;
570: if (mySuppliers.size() > 0) {
571: firstSupplier = (Organization) mySuppliers.elementAt(0);
572: }
573: newPhrase = getFactory().newPrepositionalPhrase();
574: newPhrase.setPreposition(Grammar.USESUPPLIER);
575: newPhrase.setIndirectObject(firstSupplier);
576: prepHelper.replacePrepOnTask(subtask, newPhrase);
577:
578: boolean redirectToCluster = false;
579: // If firstSupplier is myself, set the verb to SUPPLY.
580: // If firstSupplier does not have role of MultipleSupplierCapable, set the verb to SUPPLY.
581: // Otherwise, set the verb to SOURCE.
582: if (firstSupplier == Utility.findNamedOrganization(
583: getClusterName(), getOrganizationAssets())) {
584: subtask.setVerb(Verb.get(Constants.Verb.SUPPLY));
585: } else {
586: subtask.setVerb(Verb.get(Grammar.SOURCE));
587: }
588:
589: subtasks.add(subtask);
590: Workflow wf = expandHelper.makeWorkflow(getFactory(),
591: subtasks, task);
592: publishAddExpansion(expandHelper.makeExpansion(
593: getFactory(), wf));
594: }
595: return retval;
596: }
597:
598: /**
599: * Add an Expansion object to the Blackboard. This implementation automatically
600: * adds the associated Workflow and each of the Workflow's subtasks to the blackboard.
601: * @param expansion the Expansion being added to the Blackboard.
602: */
603: private boolean publishAddExpansion(Expansion expansion) {
604: publishAdd(expansion);
605: publishAdd(expansion.getWorkflow());
606:
607: Enumeration subtasks = expansion.getWorkflow().getTasks();
608: while (subtasks.hasMoreElements()) {
609: publishAdd(subtasks.nextElement());
610: }
611:
612: return true; // hack
613: }
614:
615: protected LoggingService logger;
616: protected UTILExpand expandHelper;
617: protected UTILPreference prefHelper;
618: protected UTILPrepPhrase prepHelper;
619: }
|