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: package org.cougaar.pizza.plugin;
027:
028: import org.cougaar.core.blackboard.IncrementalSubscription;
029: import org.cougaar.core.plugin.ComponentPlugin;
030: import org.cougaar.core.service.DomainService;
031: import org.cougaar.core.service.LoggingService;
032:
033: import org.cougaar.pizza.Constants;
034: import org.cougaar.pizza.asset.KitchenAsset;
035: import org.cougaar.pizza.asset.PizzaAsset;
036:
037: import org.cougaar.planning.ldm.PlanningFactory;
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.Task;
043: import org.cougaar.planning.ldm.plan.PlanElement;
044: import org.cougaar.planning.plugin.util.PluginHelper;
045:
046: import org.cougaar.util.UnaryPredicate;
047:
048: import java.util.Collection;
049: import java.util.Iterator;
050:
051: /**
052: * This plugin processes incoming pizza Order Tasks at the pizza provider
053: * agents. It matches the topping requirements (PropertyGroups on the
054: * DirectObject of the Task) with the capabilities of its Kitchen
055: * (again, PropertyGroups on the Asset) to decide if it can
056: * handle the Order.
057: */
058: public class ProcessOrderPlugin extends ComponentPlugin {
059: private LoggingService logger;
060: private DomainService domainService;
061: private IncrementalSubscription tasksSubscription; // orders
062: private IncrementalSubscription pesSubscription; // our responses
063: private IncrementalSubscription kitchenAssetSubscription; // the kitchen
064: private PlanningFactory planningFactory = null;
065:
066: // local pointer to kitchen
067: // Note that this is transient local state, that we must
068: // re-initialize from our subscription any time the plugin re-starts,
069: // which it may do on agent mobility or node restart for example
070: private transient KitchenAsset kitchen = null;
071:
072: /**
073: * Used by the binding utility through introspection to set my DomainService
074: * Services that are required for plugin usage should be set through reflection instead of explicitly
075: * getting each service from your ServiceBroker in the load method. The setter methods are called after
076: * the component is constructed but before the state methods such as initialize, load, setupSubscriptions, etc.
077: * If the service is not available at that time the component will be unloaded.
078: */
079: public void setDomainService(DomainService aDomainService) {
080: domainService = aDomainService;
081: }
082:
083: /**
084: * Set up our services and our factory.
085: */
086: public void load() {
087: super .load();
088: logger = (LoggingService) getServiceBroker().getService(this ,
089: LoggingService.class, null);
090:
091: planningFactory = (PlanningFactory) domainService
092: .getFactory("planning");
093: getServiceBroker().releaseService(this , DomainService.class,
094: domainService);
095: }
096:
097: /**
098: * Create the subscriptions to my Order Tasks, PlanElements for those, and Kitchen Asset
099: */
100: protected void setupSubscriptions() {
101: tasksSubscription = (IncrementalSubscription) getBlackboardService()
102: .subscribe(ORDER_TASKS_PRED);
103: // Subscribe to PlanElements handling the Order Tasks -
104: // to tell when we've processed a Task
105: pesSubscription = (IncrementalSubscription) getBlackboardService()
106: .subscribe(ORDER_TASKS_PES_PRED);
107: kitchenAssetSubscription = (IncrementalSubscription) getBlackboardService()
108: .subscribe(KITCHEN_ASSET_PRED);
109: }
110:
111: /**
112: * Process the subscriptions: match topping PG on Order Tasks with
113: * the PGs on the KitchenAsset, and if the Kitchen has the needed PGs,
114: * respond with SUCCESS.
115: */
116: protected void execute() {
117: // Make sure we have a kitchen asset before we allocate our tasks.
118: // We only expect 1 kitchen asset so we'll exit if we don't have
119: // one and set it when we do.
120: if (kitchenAssetSubscription.isEmpty()) {
121: return;
122: }
123:
124: // If get here, the Kitchen asset has been created
125: if (kitchen == null) {
126: // Need to get a pointer to Kitchen asset, and then
127: // try to Allocate any Tasks
128: kitchen = (KitchenAsset) kitchenAssetSubscription.first();
129:
130: // allocate all of the tasks on our subscription so far in case we
131: // missed some on the added list while our kitchen asset was null
132: allocateOrderTasks(tasksSubscription.getCollection());
133: } else {
134: // if we had our kitchen asset already, process any new tasks
135: // Right now assume we only get new tasks and no changes
136: allocateOrderTasks(tasksSubscription.getAddedCollection());
137: }
138: }
139:
140: /**
141: * Allocate the new order tasks to the pizza kitchen asset
142: *
143: * @param newOrderTasks The tasks that are ordering pizzas from our kitchen.
144: */
145: private void allocateOrderTasks(Collection newOrderTasks) {
146: for (Iterator i = newOrderTasks.iterator(); i.hasNext();) {
147: Task newTask = (Task) i.next();
148:
149: // Has this Order Task already been processed?
150: // This would happen if the agent restarted, for example.
151: // 2 approaches:
152: // 1: Check task.getPlanElement()
153: // -- quick, but mixes Subscription view with direct view, so
154: // frowned upon (may cause inconsistent state, errors)
155: // 1B: BlackBoard Query -- really just the same as the direct approach
156: // 2: Standing subscription to PEs
157: // -- Check if there's a PlanElement on our subscription that points to this Task
158:
159: // So we check to see if this Task was already planned, and avoid
160: // double-planning it
161: if (taskWasPlanned(newTask, pesSubscription)) {
162: // We apparently already handled this Task. Skip it for now
163: if (logger.isDebugEnabled())
164: logger.debug("Order already processed: " + newTask);
165: continue;
166: }
167:
168: // See if our kitchen can make the type of pizza requested and then
169: // make a successful or unsuccessful allocation result.
170: boolean kitchenCanMake = canMakePizza(newTask);
171: AllocationResult ar;
172: if (kitchenCanMake) {
173: // This helper method makes an allocation result containing all aspect values that match the current task's
174: // preferences. Set the confidence value of the allocation result to 1.0 indicating a completed result and set
175: // isSuccess to true.
176: ar = PluginHelper.createEstimatedAllocationResult(
177: newTask, planningFactory, 1.0, true);
178: } else {
179: // Since we can't make the pizza we create a new aspect value to represent the zero quantity -- no pizzas provided
180: AspectValue qtyAspectValue = AspectValue
181: .newAspectValue(AspectType.QUANTITY, 0);
182: AspectValue[] aspectValueArray = { qtyAspectValue };
183: // Use the planning factory to create a new allocation result with a confidence of 1.0 and isSuccess is false.
184: ar = planningFactory.newAllocationResult(1.0, false,
185: aspectValueArray);
186: }
187: // Design choice: The final processing of this task ends as an allocation to the kitchen asset.
188: // Another option would be to create a Disposition as the plan element instead of an
189: // allocation to an asset.
190: Allocation alloc = planningFactory.createAllocation(newTask
191: .getPlan(), newTask, kitchen, ar,
192: Constants.Roles.PIZZAPROVIDER);
193: getBlackboardService().publishAdd(alloc);
194: }
195: }
196:
197: /**
198: * Re-usable method to search a Collection (ie, Subscription) of
199: * PlanElements to see if any handle the given Task. Use this
200: * to decide whether a Task needs to be planned by this Plugin.
201: * @param t Task for which to search for a PlanElement
202: * @param planelements Collection of PlanElements to look through, typically a subscription
203: * @return true if a PlanElement in the collection points to this Task
204: */
205: private boolean taskWasPlanned(Task t, Collection planelements) {
206: if (t == null || planelements == null || planelements.isEmpty())
207: return false;
208:
209: Iterator iter = pesSubscription.iterator();
210: while (iter.hasNext()) {
211: PlanElement pe = (PlanElement) iter.next();
212: if (pe.getTask().equals(t)) {
213: if (logger.isDebugEnabled())
214: logger.debug("Order Task already planned. Task: "
215: + t);
216: return true;
217: }
218: } // end of while loop
219:
220: // No PlanElement on our subscription handles this Task
221: if (logger.isDebugEnabled())
222: logger.debug("Order Task not yet planned: " + t);
223:
224: return false;
225: }
226:
227: /**
228: * Check the kitchen asset to see if it has the toppings to make the requested type of pizza
229: *
230: * @param newTask The order task.
231: * @return true If we can make the pizza
232: */
233: private boolean canMakePizza(Task newTask) {
234: PizzaAsset directObject = (PizzaAsset) newTask
235: .getDirectObject();
236: // Compare PGs on the pizza to PGs on the kitchen
237: if (directObject.hasVeggiePG() && !kitchen.hasVeggiePG()) {
238: if (logger.isWarnEnabled()) {
239: logger
240: .warn("Can't make the VeggiePizza that was ordered!");
241: }
242: return false;
243: }
244:
245: if (directObject.hasMeatPG() && !kitchen.hasMeatPG()) {
246: if (logger.isWarnEnabled()) {
247: logger
248: .warn("Can't make the MeatPizza that was ordered!");
249: }
250: return false;
251: }
252: return true;
253: }
254:
255: /**
256: * A predicate that filters for Verb.Order tasks
257: */
258: private final static UnaryPredicate ORDER_TASKS_PRED = new UnaryPredicate() {
259: public boolean execute(Object o) {
260: if (o instanceof Task) {
261: return ((Task) o).getVerb().equals(
262: Constants.Verbs.ORDER);
263: }
264: return false;
265: }
266: };
267:
268: /**
269: * A predicate that filters for PlanElements handling Verb.Order tasks
270: */
271: private final static UnaryPredicate ORDER_TASKS_PES_PRED = new UnaryPredicate() {
272: public boolean execute(Object o) {
273: if (o instanceof PlanElement) {
274: Task t = ((PlanElement) o).getTask();
275: return t.getVerb().equals(Constants.Verbs.ORDER);
276: }
277: return false;
278: }
279: };
280:
281: /**
282: * A predicate that filters for KitchenAsset objects
283: */
284: private final static UnaryPredicate KITCHEN_ASSET_PRED = new UnaryPredicate() {
285: public boolean execute(Object o) {
286: return (o instanceof KitchenAsset);
287: }
288: };
289: } // end of ProcessOrderPlugin
|