001: /*
002: * <copyright>
003: *
004: * Copyright 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.servicediscovery.plugin;
027:
028: import org.cougaar.core.blackboard.IncrementalSubscription;
029: import org.cougaar.core.mts.MessageAddress;
030: import org.cougaar.core.plugin.ComponentPlugin;
031: import org.cougaar.core.service.DomainService;
032: import org.cougaar.core.service.LoggingService;
033: import org.cougaar.planning.ldm.PlanningDomain;
034: import org.cougaar.planning.ldm.PlanningFactory;
035: import org.cougaar.planning.ldm.asset.Entity;
036: import org.cougaar.planning.ldm.plan.AllocationResult;
037: import org.cougaar.planning.ldm.plan.Disposition;
038: import org.cougaar.planning.ldm.plan.PrepositionalPhrase;
039: import org.cougaar.planning.ldm.plan.Role;
040: import org.cougaar.planning.ldm.plan.Task;
041: import org.cougaar.planning.plugin.util.PluginHelper;
042: import org.cougaar.servicediscovery.Constants;
043: import org.cougaar.servicediscovery.SDDomain;
044: import org.cougaar.servicediscovery.SDFactory;
045: import org.cougaar.servicediscovery.description.MMRoleQuery;
046: import org.cougaar.servicediscovery.description.ScoredServiceDescription;
047: import org.cougaar.servicediscovery.description.ServiceClassification;
048: import org.cougaar.servicediscovery.description.ServiceDescription;
049: import org.cougaar.servicediscovery.description.ServiceInfoScorer;
050: import org.cougaar.servicediscovery.description.ServiceRequest;
051: import org.cougaar.servicediscovery.transaction.MMQueryRequest;
052: import org.cougaar.servicediscovery.transaction.ServiceContractRelay;
053: import org.cougaar.servicediscovery.util.RoleScorer;
054: import org.cougaar.servicediscovery.util.UDDIConstants;
055: import org.cougaar.util.TimeSpan;
056: import org.cougaar.util.UnaryPredicate;
057:
058: import java.util.Collection;
059: import java.util.Iterator;
060:
061: /**
062: * Simple ServiceDiscovery ClientPlugin, responsible for initiating queries for services,
063: * taking the resulting service pointer, and sending a Relay to the Provider requesting service.
064: *<p>
065: * Service Discovery is initiated by receiving a Find Providers task, and looking for the single Role
066: * indicated on the AS Prepositional Phrase.
067: * Sends a Disposition on the FindProviders Task when it gets a Relationship
068: * with a provider for the service.
069: *<p>
070: * This Plugin is written to be extended (or copied). See the pizza module for an example of doing this.
071: *<p>
072: * Limitations: not quiescent aware, doesn't handle time-phased relationships,
073: * not guaranteed to work with persistence and restarts, and assumes that providers
074: * will not revoke relationships or otherwise change their mind.
075: */
076: public class SimpleSDClientPlugin extends ComponentPlugin {
077: /** Our queries to the Matchmaker */
078: private IncrementalSubscription myMMRequestSubscription;
079: /** Our requests to Providers */
080: private IncrementalSubscription myServiceContractRelaySubscription;
081: /** FindProviders Tasks asking us to do work */
082: private IncrementalSubscription myFindProvidersTaskSubscription;
083:
084: protected LoggingService myLoggingService;
085: private DomainService myDomainService;
086:
087: // Used to create ServiceContractRelay, etc
088: private SDFactory mySDFactory;
089:
090: // Used to create AllocationResults, Dispositions, etc
091: private PlanningFactory myPlanningFactory;
092:
093: // A pointer to the local (self) entity
094: private Entity mySelfEntity;
095:
096: /**
097: * Used by the binding utility through reflection to set my DomainService
098: */
099: public void setDomainService(DomainService domainService) {
100: myDomainService = domainService;
101: }
102:
103: /**
104: * Get the DomainService set by reflection, for getting pointers to Factories.
105: */
106: protected DomainService getDomainService() {
107: return myDomainService;
108: }
109:
110: /**
111: * Get the PlanningFactory
112: */
113: protected PlanningFactory getPlanningFactory() {
114: return myPlanningFactory;
115: }
116:
117: /**
118: * Get the SDFactory. Factory for service discovery domain
119: */
120: protected SDFactory getSDFactory() {
121: return mySDFactory;
122: }
123:
124: /**
125: * Load other services, particularly those not essential to operations.
126: * In this case, use the DomainService to get Factories and release it.
127: * Also get the Logging Service.
128: */
129: public void load() {
130: super .load();
131:
132: myLoggingService = (LoggingService) getServiceBroker()
133: .getService(this , LoggingService.class, null);
134: if (myLoggingService == null)
135: myLoggingService = LoggingService.NULL;
136:
137: mySDFactory = (SDFactory) getDomainService().getFactory(
138: SDDomain.SD_NAME);
139: myPlanningFactory = (PlanningFactory) getDomainService()
140: .getFactory(PlanningDomain.PLANNING_NAME);
141: }
142:
143: /** Release any services retrieved during load() -- in this case, the LoggingService and DomainService*/
144: public void unload() {
145: if (myLoggingService != LoggingService.NULL) {
146: getServiceBroker().releaseService(this ,
147: LoggingService.class, myLoggingService);
148: myLoggingService = LoggingService.NULL;
149: }
150:
151: if (myDomainService != null) {
152: getServiceBroker().releaseService(this ,
153: DomainService.class, myDomainService);
154: setDomainService(null);
155: }
156:
157: myPlanningFactory = null;
158: mySDFactory = null;
159: super .unload();
160: }
161:
162: /**
163: * Subscribe to MMRequests (requests to the Matchmaker),
164: * ServiceContractRelays (where we'll get responses from the Providers),
165: * and FindProviders Tasks (requests to us to do ServiceDiscovery).
166: */
167: protected void setupSubscriptions() {
168: myMMRequestSubscription = (IncrementalSubscription) getBlackboardService()
169: .subscribe(MYMMREQUESTPREDICATE);
170: myServiceContractRelaySubscription = (IncrementalSubscription) getBlackboardService()
171: .subscribe(MYSERVICECONTRACTRELAYPREDICATE);
172: myFindProvidersTaskSubscription = (IncrementalSubscription) getBlackboardService()
173: .subscribe(getFindProvidersPredicate());
174: }
175:
176: /**
177: * Issue MMQueries for FindProviders Tasks, ServiceContractRelays for responses to MMQueries,
178: * and Dispositions on the FindProviders Tasks when those Relays are filled in with a Contract.
179: *<p>
180: * For each new FindProviders Task, issue an MMQuery to the Matchmaker, looking for the
181: * specified Role.
182: * If an MMQuery changes, issue a new ServiceContractRelay to the first named Provider
183: * that was found.
184: * If a ServiceContractRelay changes, update the Disposition on the original FindProviders
185: * Task if we've now found a Provider for the needed Service.
186: */
187: protected void execute() {
188: // One if block below per subscription
189:
190: // Look for new FindProviders Tasks -- requests for Services
191: if (myFindProvidersTaskSubscription.hasChanged()) {
192: // If we've been asked to do more ServiceDiscovery
193: Collection adds = myFindProvidersTaskSubscription
194: .getAddedCollection();
195:
196: for (Iterator addIterator = adds.iterator(); addIterator
197: .hasNext();) {
198: // Grab the task
199: Task findProviderTask = (Task) addIterator.next();
200: // Get the requested role from the Task.
201: Role taskRole = getRole(findProviderTask);
202:
203: // Send the query for the given Role, because of the given Task.
204: queryServices(taskRole, findProviderTask);
205: }
206:
207: // We don't handle removes or changes to the FindProviders...
208: if (myLoggingService.isDebugEnabled()) {
209: Collection changes = myFindProvidersTaskSubscription
210: .getChangedCollection();
211: Collection removes = myFindProvidersTaskSubscription
212: .getRemovedCollection();
213: if (!changes.isEmpty() || !removes.isEmpty()) {
214: myLoggingService
215: .debug("execute: ignoring changed/deleted FindProvider tasks - "
216: + " changes = "
217: + changes
218: + ", removes = " + removes);
219: }
220: }
221: } // end of block to handle FindProviders Tasks
222:
223: // Look for responses on our queries to the Matchmaker for YP lookups
224: if (myMMRequestSubscription.hasChanged()) {
225: for (Iterator iterator = myMMRequestSubscription
226: .getChangedCollection().iterator(); iterator
227: .hasNext();) {
228: MMQueryRequest mmRequest = (MMQueryRequest) iterator
229: .next();
230:
231: if (myLoggingService.isDebugEnabled()) {
232: myLoggingService
233: .debug("execute: MMQueryRequest has changed."
234: + mmRequest);
235: }
236:
237: // Get the request
238: Collection services = mmRequest.getResult();
239: if (services != null) {
240: // Debugging: print all results
241: if (myLoggingService.isDebugEnabled()) {
242:
243: myLoggingService.debug("Results for query "
244: + mmRequest.getQuery().toString());
245: for (Iterator serviceIterator = services
246: .iterator(); serviceIterator.hasNext();) {
247: ScoredServiceDescription serviceDescription = (ScoredServiceDescription) serviceIterator
248: .next();
249: myLoggingService.debug("Score "
250: + serviceDescription.getScore());
251: myLoggingService.debug("Provider name "
252: + serviceDescription
253: .getProviderName());
254: myLoggingService.debug("*********");
255: }
256: } /// End of debugging....
257:
258: // For each service
259: for (Iterator serviceIterator = services.iterator(); serviceIterator
260: .hasNext();) {
261: ScoredServiceDescription serviceDescription = (ScoredServiceDescription) serviceIterator
262: .next();
263:
264: // Debugging...
265: if (myLoggingService.isDebugEnabled()) {
266: myLoggingService
267: .debug("execute: - provider: "
268: + serviceDescription
269: .getProviderName()
270: + " score: "
271: + serviceDescription
272: .getScore());
273: } // end debug
274:
275: // Request a Service Contract for it
276: requestServiceContract(serviceDescription);
277:
278: // only want one contract, no matter how many are in loop
279: if (myLoggingService.isDebugEnabled()
280: && serviceIterator.hasNext())
281: myLoggingService
282: .debug("Had more than one service: "
283: + services.size());
284: break;
285: }
286:
287: // Done with the query so could clean up, but leave it for the MatchmakeryQueryServlet
288: //getBlackboardService().publishRemove(mmRequest);
289: }
290: }
291: } // end of block to handle response from the Matchmaker
292:
293: // Look for responses on our queries to potential providers
294: if (myServiceContractRelaySubscription.hasChanged()) {
295: Collection changedRelays = myServiceContractRelaySubscription
296: .getChangedCollection();
297:
298: // Update disposition on FindProviders task
299: if (changedRelays.size() > 0) {
300: if (myLoggingService.isDebugEnabled()) {
301: myLoggingService.debug("changedRelays.size = "
302: + changedRelays.size()
303: + ", updateFindProvidersTaskDispositions");
304: }
305: updateFindProvidersTaskDispositions(changedRelays);
306: }
307: } // end of block to handle changed ServiceContractRelays
308: } // end of execute
309:
310: /////////
311: // Methods to get the query to the Matchmaker...
312:
313: /**
314: * Return the Role (indirect object) on the Constants.Prepositions.AS Prep Phrase in the given
315: * FindProviders Task, if any -- this is the Provider Role we will look for.
316: *
317: * @param findProvidersTask Task which should contain an AS Prepositional Phrase
318: * @return Role on AS Phrase, indicate Role desired from ServiceDiscovery -- may be null
319: */
320: protected Role getRole(Task findProvidersTask) {
321: PrepositionalPhrase asPhrase = findProvidersTask
322: .getPrepositionalPhrase(org.cougaar.planning.Constants.Preposition.AS);
323:
324: if (asPhrase == null) {
325: return null;
326: } else {
327: return (Role) asPhrase.getIndirectObject();
328: }
329: }
330:
331: /**
332: * Send an {@link MMQueryRequest} to the Matchmaker, requesting a provider
333: * with the given {@link Role}, satisfying the given Task.
334: *<p>
335: * Extenders of this plugin might choose to over-ride this method, though it usually will
336: * not be necessary.
337: *
338: * @param role The {@link Role} to request
339: * @param findProvidersTask The Task whose request this will satisfy
340: */
341: protected void queryServices(Role role, Task findProvidersTask) {
342: MMQueryRequest mmRequest;
343:
344: if (myLoggingService.isInfoEnabled())
345: myLoggingService
346: .info("queryServices asking MatchMaker for: "
347: + role + " because of task "
348: + findProvidersTask);
349:
350: // Create a scoring function for the Matchmaker to use to weigh providers
351: ServiceInfoScorer sis = getServiceInfoScorer(role,
352: findProvidersTask);
353:
354: // Now create the set of YP / SD structures, publishing the request on the BlackBoard,
355: // for the MatchmakerPlugin to pick up
356: MMRoleQuery roleQuery = new MMRoleQuery(role, sis);
357: mmRequest = getSDFactory().newMMQueryRequest(roleQuery);
358: getBlackboardService().publishAdd(mmRequest);
359: }// end of queryServices
360:
361: /**
362: * Create a scoring function to weigh different services registered in the YP, for use
363: * by the Matchmaker.
364: * In this case, create a function that requires the given (Commercial) role.
365: *<p>
366: * Extenders of this plugin may over-ride this method to use a different function.
367: *
368: * @param role the Role to look for
369: * @param fpTask The FindProviders task this will satisfy
370: * @return the scoring function to hand the Matchmaker
371: */
372: protected ServiceInfoScorer getServiceInfoScorer(Role role,
373: Task fpTask) {
374: // Create a Service Scorer that looks for a provider with the given role,
375: return new RoleScorer(role);
376: }
377:
378: ///////////
379: // Support methods for tasking Matchmaker response and requesting the service from the Provider
380:
381: /**
382: * Create and publish a relay with a service request for the specified Role, to the Provider
383: * specified in the serviceDescription.
384: *
385: * @param serviceDescription the Matchmaker tells us about the possible service provider in this Service Description
386: */
387: protected void requestServiceContract(
388: ServiceDescription serviceDescription) {
389: // Pull the Role off the Commercial Service Scheme in this serviceDescription
390: Role role = getRole(serviceDescription);
391: if (role == null) {
392: myLoggingService
393: .error("Error requesting service contract: a null Role in serviceDescription: "
394: + serviceDescription);
395: } else {
396: // Create a request good for all time
397: TimeSpan timeSpan = TimeSpan.FOREVER;
398: String providerName = serviceDescription.getProviderName();
399:
400: // Create a ServiceRequest from the self Entity, for the given Role,
401: // for the specified time period (forever)
402: ServiceRequest request = getSDFactory().newServiceRequest(
403: getLocalEntity(), role,
404: getSDFactory().createTimeSpanPreferences(timeSpan));
405:
406: // Send that ServiceRequest in a Relay to the given Provider
407: ServiceContractRelay relay = getSDFactory()
408: .newServiceContractRelay(
409: MessageAddress
410: .getMessageAddress(providerName),
411: request);
412: getBlackboardService().publishAdd(relay);
413: }
414: } // end of requestServiceContract
415:
416: /**
417: * Retrieve the Role that the Provider is offering out of the ServiceDescription.
418: * Note that this assumes the role is under the CommercialServiceScheme, using the
419: * Domain's Constants file. Extenders will over-ride this method.
420: *
421: * @param serviceDescription The Description of the Service as advertised in the YP.
422: * @return the Role advertised by the Provider, or null if none found.
423: */
424: protected Role getRole(ServiceDescription serviceDescription) {
425: // A serviceDescription may have multiple Classifications
426: for (Iterator iterator = serviceDescription
427: .getServiceClassifications().iterator(); iterator
428: .hasNext();) {
429: ServiceClassification serviceClassification = (ServiceClassification) iterator
430: .next();
431: // We have placed the Roles providers are registering in the CommercialServiceScheme
432: if (serviceClassification.getClassificationSchemeName()
433: .equals(UDDIConstants.COMMERCIAL_SERVICE_SCHEME)) {
434: Role role = Role.getRole(serviceClassification
435: .getClassificationName());
436: return role;
437: }
438: }
439: return null;
440: }
441:
442: /**
443: * Get the local (self) Entity - from a local cache, or by querying the Blackboard.
444: * @return the local (self) Entity
445: */
446: protected Entity getLocalEntity() {
447: if (mySelfEntity == null) {
448: Collection entities = getBlackboardService().query(
449: new UnaryPredicate() {
450: public boolean execute(Object o) {
451: if (o instanceof Entity) {
452: return ((Entity) o).isLocal();
453: }
454: return false;
455: }
456: });
457:
458: if (!entities.isEmpty()) {
459: mySelfEntity = (Entity) entities.iterator().next();
460: }
461: }
462: return mySelfEntity;
463: }
464:
465: //////////
466: // Methods for responding to the ServiceDiscovery requester when the ServiceContract is fulfilled
467:
468: /**
469: * For each ServiceContractRelay that changes and has a ServiceContract, find the
470: * un-disposed FindProviders Task that requested a Provider with the given Role, and
471: * dispose it as succesful.
472: *<p>
473: * Assumes there will be one un-disposed FindProviders Task per Role.
474: */
475: protected void updateFindProvidersTaskDispositions(
476: Collection changedServiceContractRelays) {
477: if (myFindProvidersTaskSubscription.isEmpty()) {
478: // Nothing to update
479: return;
480: }
481:
482: // For each answered Relay
483: for (Iterator relayIterator = changedServiceContractRelays
484: .iterator(); relayIterator.hasNext();) {
485: ServiceContractRelay relay = (ServiceContractRelay) relayIterator
486: .next();
487:
488: // If it includes a Contract
489: if (relay.getServiceContract() != null) {
490: Role relayRole = relay.getServiceContract()
491: .getServiceRole();
492:
493: // Look through my FindProviders Tasks
494: for (Iterator taskIterator = myFindProvidersTaskSubscription
495: .iterator(); taskIterator.hasNext();) {
496: Task findProvidersTask = (Task) taskIterator.next();
497:
498: Disposition disposition = (Disposition) findProvidersTask
499: .getPlanElement();
500:
501: // I'm looking for an incomplete one
502: if (disposition == null) {
503: // Get the Role that this was looking for
504: Role taskRole = getRole(findProvidersTask);
505:
506: // Assuming only 1 open (un-disposed) task per Role.
507: if (taskRole.equals(relayRole)) {
508: // So - they asked for a Role for which we now have a Service Contract.
509: // Create a Disposition for the Task, with an EstimatedResult saying success
510:
511: // The estAR is for the findProvidersTask, created by the planningFactory,
512: // with a confidence of 1.0, success=true
513: AllocationResult estResult = PluginHelper
514: .createEstimatedAllocationResult(
515: findProvidersTask,
516: getPlanningFactory(), 1.0,
517: true);
518: // The disposition should be the same Plan as the Task, for the findProvidersTask,
519: // and include the new success estimatedResult
520: disposition = getPlanningFactory()
521: .createDisposition(
522: findProvidersTask.getPlan(),
523: findProvidersTask,
524: estResult);
525:
526: getBlackboardService().publishAdd(
527: disposition);
528: }
529: }
530: } // end of loop over FindProviders tasks
531: }
532: } // end of loop over changed ServiceContractRelays
533: } // end of updateFindProvidersTaskDispositions
534:
535: ////////////////
536: // Now the Unary Predicates for use with subscriptions.
537: // Static Final cause we only need the one, unchanging instance.
538: // Names in all caps since they're constants.
539:
540: private static final UnaryPredicate MYSERVICECONTRACTRELAYPREDICATE = new UnaryPredicate() {
541: public boolean execute(Object o) {
542: return (o instanceof ServiceContractRelay);
543: }
544: };
545:
546: private static final UnaryPredicate MYMMREQUESTPREDICATE = new UnaryPredicate() {
547: public boolean execute(Object o) {
548: return (o instanceof MMQueryRequest);
549: }
550: };
551:
552: /**
553: * Predicate to get the FindProviders Tasks. In the off-chance that your verb is
554: * slightly different, you can over-ride this method to point to your
555: * domain-specific Verb Constant.
556: *<b>
557: * Note that it is not a constant, just so it can be over-ridden.
558: */
559: protected UnaryPredicate getFindProvidersPredicate() {
560: return new UnaryPredicate() {
561: public boolean execute(Object o) {
562: if (o instanceof Task) {
563: return ((Task) o).getVerb().equals(
564: Constants.Verbs.FindProviders);
565: } else {
566: return false;
567: }
568: }
569: };
570: }
571: }
|