001: /*
002: * <copyright>
003: *
004: * Copyright 2002-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: package org.cougaar.pizza.plugin;
027:
028: import org.cougaar.core.blackboard.IncrementalSubscription;
029: import org.cougaar.core.blackboard.Subscription;
030: import org.cougaar.pizza.Constants;
031: import org.cougaar.planning.ldm.asset.Asset;
032: import org.cougaar.planning.ldm.asset.Entity;
033: import org.cougaar.planning.ldm.plan.Allocation;
034: import org.cougaar.planning.ldm.plan.Disposition;
035: import org.cougaar.planning.ldm.plan.Expansion;
036: import org.cougaar.planning.ldm.plan.NewPrepositionalPhrase;
037: import org.cougaar.planning.ldm.plan.NewTask;
038: import org.cougaar.planning.ldm.plan.PrepositionalPhrase;
039: import org.cougaar.planning.ldm.plan.Relationship;
040: import org.cougaar.planning.ldm.plan.RelationshipSchedule;
041: import org.cougaar.planning.ldm.plan.SubTaskResult;
042: import org.cougaar.planning.ldm.plan.Task;
043: import org.cougaar.util.Filters;
044: import org.cougaar.util.UnaryPredicate;
045:
046: import java.util.Collection;
047: import java.util.Iterator;
048: import java.util.Vector;
049:
050: /**
051: * The SDPlaceOrderPlugin extends the {@link PlaceOrderPlugin} to use Service Discovery
052: * to find pizza providers dynamically. Once the plugin receives the {@link PizzaPreferences}
053: * object, it publishes a {@link org.cougaar.pizza.Constants.Verbs#FIND_PROVIDERS} task with a Role of {@link org.cougaar.pizza.Constants.Roles#PIZZAPROVIDER}. This task
054: * will be handled by Service Discovery. The plugin then creates and expands the {@link org.cougaar.pizza.Constants.Verbs#ORDER}
055: * Task as done in the super class. However, unlike the super class, this plugin waits
056: * until Service Discovery has finished finding a pizza provider and has added a
057: * Disposition on the FindProviders task before it can allocate the pizza subtasks.
058: * <p/>
059: * ServiceDiscovery will find a provider, creating a PizzaProvider relationship. Then
060: * the plugin continues as in the PlaceOrderPlugin.
061: * <p/>
062: * If the Expansion on the order task fails, i.e., the provider could not complete the
063: * pizza order, then the plugin will remove the Allocations on the subtasks of the Expansion and
064: * request a new provider from Service Discovery. To exclude the provider that
065: * previously failed, the plugin adds a prepositional phrase of {@link org.cougaar.pizza.Constants.Prepositions#NOT} with the name of the
066: * provider as the indirect object on the FindProviders task. Once a new provider is
067: * found the tasks will be reallocated -- hopefully more succesfully!
068: */
069: public class SDPlaceOrderPlugin extends PlaceOrderPlugin {
070: // Subscription to Dispositions on FindProviders tasks
071: private IncrementalSubscription fpDispositionSub;
072: // Subscription to Order tasks, entire collection not just incremental changes
073: private Subscription taskSub;
074:
075: /**
076: * Overrides the super class method. Adds additional subscriptions
077: * to Dispositions of the FindProviders Task and our original Order Tasks.
078: */
079: protected void setupSubscriptions() {
080: // Initialize subscriptions in the super class.n
081: super .setupSubscriptions();
082: // Subscribe to the Disposition of the FindProviders task
083: fpDispositionSub = (IncrementalSubscription) blackboard
084: .subscribe(FP_DISPOSITION_PRED);
085: // Subscribe to our Order tasks.
086: taskSub = blackboard.subscribe(TASK_PRED);
087: }
088:
089: /**
090: * When we get the PizzaPreferences object (that subscription is changed), ask ServiceDiscovery to find a provider.
091: * Meanwhile, create the root order Task and its expansions - but not yet allocated.
092: * <p/>
093: * When the FindProviders task is Disposed (a different subscription change),
094: * we have a provider. Allocate the Order sub-tasks to that provider.
095: * <p/>
096: * As in the base class, update the AllocationResults as they come in from our provider.
097: * <p/>
098: * Finally, when the Expansion of the root Order Task changes, see if we got all
099: * our pizza. If not, remove the old Allocations to the old provider, and ask
100: * ServiceDiscovery to find another (different) provider. When that FindProviders
101: * is disposed, we'll try again...
102: */
103: protected void execute() {
104: // The PizzaPreferences object contains the party invitation responses,
105: // and indicates we should start.
106: // Get any just added PizzaPreferences object.
107: PizzaPreferences pizzaPrefs = getPizzaPreferences();
108: if (pizzaPrefs != null) {
109: // We get in here only when a PizzaPreferences object has just been added. So
110: // in our application, this should happen exactly once.
111:
112: // Use service discovery to find a provider, null means do not exclude any providers
113: publishFindProvidersTask(null);
114: // Create the parent order task.
115: Task orderTask = createOrderTask();
116: // Create a subtask for each pizza preference.
117: Collection pizzaSubtasks = createPizzaSubtasks(pizzaPrefs,
118: orderTask);
119: // Expand the order task and add the subtasks to the workflow.
120: makeExpansion(orderTask, pizzaSubtasks);
121: }
122:
123: // Service Discovery adds a Disposition to the FindProviders task when it is done.
124: Disposition disposition = getDisposedFindProvider();
125: if (disposition != null) {
126: /**
127: * Overrides super method. Uses the Disposition to get to the FindProviders task to
128: * check for excluded providers. An excluded provider is one that was previously
129: * unable to complete an order task.
130: */
131: Entity pizzaProvider = getProvider(disposition);
132: if (pizzaProvider != null) {
133: // Allocate all pizza subtasks to this single pizza provider.
134: allocateSubtasks(getUnallocatedSubtasks(),
135: pizzaProvider);
136: }
137: }
138:
139: // Update changes to the results of the allocation.
140: updateOrderAllocationResults();
141:
142: // Check for changes on the Expansion
143: Expansion exp = getChangedExpansion();
144: if (exp != null) {
145: if (logger.isDebugEnabled()) {
146: logger.debug(" Change received on the expansion "
147: + printExpansionResults(exp));
148: }
149:
150: // If the Expansion is confident, then we are done. Print the results.
151: if (exp.getReportedResult().getConfidenceRating() == 1.0) {
152: logExpansionResults(exp);
153:
154: // But this is using servicediscovery, so we could try again.
155: if (!exp.getReportedResult().isSuccess()) {
156: /**
157: * Publish remove the Allocations on all subtasks in the Expansion and return the
158: * provider that failed. The subtasks will get reallocated when a new provider is
159: * found.
160: */
161: Entity failedProvider = processFailedSubtasks(exp);
162:
163: // Request a new provider from Service Discovery, excluding the one that failed.
164: // We do so by publishing a new FindProviders task. When ServiceDiscovery
165: // Disposes that Task, this plugin will run again, and re-allocate
166: // the Order Tasks....
167: publishFindProvidersTask(failedProvider);
168: logger
169: .shout("Initial Expansion FAILed. Redo Service Discovery.");
170: }
171: }
172: }
173: }
174:
175: /**
176: * Publishes a FindProviders task, indicating to Service Discovery
177: * the desired Role for which you want a relationship (PizzaProvider),
178: * and possibly a known provider to ignore.
179: * <p/>
180: * The Task is created with the Verb
181: * FindProviders and the direct object is set to the agent's self entity. The type of
182: * provider is defined by creating an "As" Preposition with the indirect object set to a
183: * PizzaProvider Role. If the failedProvider is not null, a "Not" Preposition is also
184: * created and the indirect object is set to the failed provider asset. The "Not"
185: * Preposition represents a provider to be excluded from Service Discovery. The
186: * task is then published to the blackboard.
187: *
188: * @param failedProvider the provider to exclude in service discovery. Can be null if
189: * there is no provider to exclude.
190: */
191: private void publishFindProvidersTask(Asset failedProvider) {
192: NewTask newTask = makeTask(Constants.Verbs.FIND_PROVIDERS,
193: getSelfEntity());
194: Vector prepPhrases = new Vector();
195:
196: // Indicate we want an Agent to act AS a PizzaProvider
197: NewPrepositionalPhrase pp = planningFactory
198: .newPrepositionalPhrase();
199: pp
200: .setPreposition(org.cougaar.planning.Constants.Preposition.AS);
201: pp.setIndirectObject(Constants.Roles.PIZZAPROVIDER);
202: prepPhrases.add(pp);
203:
204: if (failedProvider != null) {
205: // Indicate we want to exclude this known provider
206: NewPrepositionalPhrase excludePhrase = planningFactory
207: .newPrepositionalPhrase();
208: excludePhrase.setPreposition(Constants.Prepositions.NOT);
209: excludePhrase.setIndirectObject(failedProvider);
210: prepPhrases.add(excludePhrase);
211: }
212: newTask.setPrepositionalPhrases(prepPhrases.elements());
213: getBlackboardService().publishAdd(newTask);
214: }
215:
216: /**
217: * Find a PizzaProvider to try using, avoiding the provider on the given
218: * Disposition if any.
219: * First, retrieve the excluded provider from the
220: * FindProviders task of the Disposition. Get all relationships that match the Role
221: * of PizzaProvider from the RelationshipSchedule. Return the first provider found that
222: * is not the excluded provider. Returns null if a provider is not found.
223: * <p/>
224: * Note that only one failed provider can be avoided with this implementation.
225: *
226: * @param disposition whose previous Provider to avoid
227: * @return a pizza provider Entity to try
228: */
229: protected Entity getProvider(Disposition disposition) {
230: // Get the excluded provider from the FindProviders task of the disposition.
231: Entity excludeProvider = null;
232: if (disposition != null)
233: excludeProvider = getExcludedProvider(disposition.getTask());
234:
235: // Get the RelationshipSchedule for this agent.
236: RelationshipSchedule relSched = getSelfEntity()
237: .getRelationshipSchedule();
238:
239: // Find all Relationships matching the Role of PizzaProvider.
240: Collection relationships = relSched
241: .getMatchingRelationships(Constants.Roles.PIZZAPROVIDER);
242:
243: Entity provider = null;
244: for (Iterator iterator = relationships.iterator(); iterator
245: .hasNext();) {
246: Relationship r = (Relationship) iterator.next();
247: provider = (Entity) relSched.getOther(r);
248: // Return the first provider found that is not the excluded provider
249: if (!provider.equals(excludeProvider)) {
250: return provider;
251: }
252: }
253: return null;
254: }
255:
256: /**
257: * Return the succesful Disposition of the FindProviders task, if it is there.
258: *
259: * @return the FindProviders Disposition, null if it is not sucessful and confident
260: */
261: private Disposition getDisposedFindProvider() {
262: for (Iterator i = fpDispositionSub.getAddedCollection()
263: .iterator(); i.hasNext();) {
264: Disposition disposition = (Disposition) i.next();
265: if (disposition.isSuccess()
266: && disposition.getEstimatedResult()
267: .getConfidenceRating() == 1.0) {
268: return disposition;
269: }
270: }
271: return null;
272: }
273:
274: /**
275: * Returns a collection of tasks that do not have PlanElements. Filters the
276: * task subscription by applying the "tasks with no PlanElement" Predicate.
277: * The point of this being that only tasks without PlanElements are the
278: * subtasks that need to be allocated.
279: *
280: * @return a collection of Tasks that do not have Allocation PlanElements.
281: */
282: private Collection getUnallocatedSubtasks() {
283: return Filters.filter((Collection) taskSub, TASK_NO_PE_PRED);
284: }
285:
286: /**
287: * Processes the subtasks of the failed Expansion and returns the provider that failed.
288: * If any of the subtasks in the expansion are failed by the provider, the subtask
289: * Allocations are publishRemoved so that they can be reallocated to a new provider.
290: * Returns the provider that failed (so we can avoid it in future).
291: * <p/>
292: * Note that the assumption is that there is only one failed provider. All the
293: * Allocations will be removed, but only the last failed provider is returned,
294: * to be excluded.
295: *
296: * @param exp order task expansion that contains our pizza orders.
297: * @return PizzaProvider that failed to satisfy our order
298: */
299: private Entity processFailedSubtasks(Expansion exp) {
300: Asset failedProvider = null;
301: for (Iterator resultsIt = exp.getWorkflow().getSubtaskResults()
302: .iterator(); resultsIt.hasNext();) {
303: SubTaskResult result = (SubTaskResult) resultsIt.next();
304: // Grab the provider to be excluded
305: failedProvider = ((Allocation) result.getTask()
306: .getPlanElement()).getAsset();
307: // Rescind all task allocations
308: getBlackboardService().publishRemove(
309: result.getTask().getPlanElement());
310: }
311: return (Entity) failedProvider;
312: }
313:
314: /**
315: * Returns the excluded provider Entity. Looks for the "Not" Preposition on the
316: * FindProviders task and gets the indirect object which is the provider entity.
317: * Presumably this provider is unable to satisfy previous pizza orders. Returns null if
318: * the FindProviders task does not contain the "Not" Preposition.
319: *
320: * @param findProvidersTask the task to look at.
321: * @return The entity (provider) to exclude.
322: */
323: private Entity getExcludedProvider(Task findProvidersTask) {
324: Entity excludedEntity = null;
325: PrepositionalPhrase notPP = findProvidersTask
326: .getPrepositionalPhrase(Constants.Prepositions.NOT);
327: if (notPP != null) {
328: excludedEntity = (Entity) notPP.getIndirectObject();
329: }
330: return excludedEntity;
331: }
332:
333: /**
334: * This predicate matches Dispositions on FindProviders tasks.
335: */
336: private static final UnaryPredicate FP_DISPOSITION_PRED = new UnaryPredicate() {
337: public boolean execute(Object o) {
338: if (o instanceof Disposition) {
339: Task task = ((Disposition) o).getTask();
340: return task.getVerb().equals(
341: Constants.Verbs.FIND_PROVIDERS);
342: }
343: return false;
344: }
345: };
346:
347: /**
348: * This predicate matches Order tasks.
349: */
350: private static final UnaryPredicate TASK_PRED = new UnaryPredicate() {
351: public boolean execute(Object o) {
352: if (o instanceof Task) {
353: Task task = (Task) o;
354: return (task.getVerb().equals(Constants.Verbs.ORDER));
355: }
356: return false;
357: }
358: };
359:
360: /**
361: * This predicate matches tasks without PlanElements. It is used on a local collection
362: * of order tasks. It should be noted that this predicate is testing a mutable attribute
363: * on a task and is therefore not recommended for use with blackboard subscriptions.
364: */
365: private static final UnaryPredicate TASK_NO_PE_PRED = new UnaryPredicate() {
366: public boolean execute(Object o) {
367: return (((Task) o).getPlanElement() == null);
368: }
369: };
370: }
|