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:
027: package org.cougaar.core.adaptivity;
028:
029: import java.util.ArrayList;
030: import java.util.HashMap;
031: import java.util.HashSet;
032: import java.util.Iterator;
033: import java.util.List;
034: import java.util.Map;
035: import java.util.Set;
036:
037: import org.cougaar.core.blackboard.Subscription;
038: import org.cougaar.core.component.ServiceBroker;
039: import org.cougaar.core.plugin.ServiceUserPlugin;
040: import org.cougaar.core.qos.metrics.MetricsService;
041: import org.cougaar.core.qos.metrics.StandardVariableEvaluator;
042: import org.cougaar.core.qos.metrics.VariableEvaluator;
043: import org.cougaar.core.service.ConditionService;
044: import org.cougaar.core.service.OperatingModeService;
045: import org.cougaar.core.service.PlaybookReadService;
046: import org.cougaar.core.service.UIDService;
047: import org.cougaar.util.UnaryPredicate;
048:
049: /**
050: * Sets OperatingModes for components based on plays in the playbook
051: * and current conditions. Runs periodically and selects new plays
052: * according to the prevailing {@link Condition}s.
053: **/
054: public class AdaptivityEngine extends ServiceUserPlugin {
055: private static final long MISSING_CONDITION_DELAY = 60000;
056:
057: /**
058: * A listener that listens to itself. It responds true when it is
059: * itself the object of a subscription change.
060: **/
061: private static class Listener extends
062: OperatingModeService.ListenerAdapter implements
063: ConditionService.Listener, PlaybookReadService.Listener,
064: OperatingModeService.Listener, UnaryPredicate {
065: public boolean execute(Object o) {
066: return (this == o);
067: }
068:
069: public boolean wantAdds() {
070: return true;
071: }
072: }
073:
074: private PlayHelper helper;
075: private PlaybookReadService playbookService;
076: private OperatingModeService operatingModeService;
077: private ConditionService conditionService;
078: private UIDService uidService;
079: private MetricsService metricsService;
080: private VariableEvaluator variableEvaluator;
081:
082: private static Class[] requiredServices = {
083: PlaybookReadService.class, OperatingModeService.class,
084: ConditionService.class, UIDService.class,
085: MetricsService.class };
086:
087: private Subscription conditionListenerSubscription;
088: private Subscription playbookListenerSubscription;
089: private Subscription operatingModeListenerSubscription;
090:
091: private Map smMap = new HashMap();
092:
093: /**
094: * Keeps track of the remote operating mode constraints we have
095: * created by name.
096: **/
097: private Map romcMap = new HashMap();
098:
099: /**
100: * Keeps a copy of romcMap while updating romcMap. Declared as
101: * instance variable to avoid consing a new one every time.
102: **/
103: private Map tempROMCMap = new HashMap();
104:
105: /**
106: * The names of the changed remote operating mode constraints.
107: * Declared as instance variable to avoid consing a new one every
108: * time.
109: **/
110: private Set romcChanges = new HashSet();
111:
112: private Play[] plays;
113:
114: private List missingConditions = new ArrayList();
115:
116: private long missingConditionTime = System.currentTimeMillis()
117: + MISSING_CONDITION_DELAY;
118:
119: private Listener playbookListener = new Listener();
120:
121: private Listener conditionListener = new Listener();
122:
123: private Listener operatingModeListener = new Listener();
124:
125: public AdaptivityEngine() {
126: super (requiredServices);
127: }
128:
129: /**
130: * Test if the services we need to run have all been acquired. We
131: * use the non-null value of the primary service (playbookService)
132: * to indicate that all services have been acquired. If
133: * playbookService has not been set, we use the
134: * super.acquireServices to perform the test of whether all services
135: * are available or not.
136: **/
137: protected boolean haveServices() {
138: if (playbookService != null)
139: return true;
140: if (acquireServices()) {
141: ServiceBroker sb = getServiceBroker();
142: playbookService = (PlaybookReadService) sb.getService(this ,
143: PlaybookReadService.class, null);
144: operatingModeService = (OperatingModeService) sb
145: .getService(this , OperatingModeService.class, null);
146: conditionService = (ConditionService) sb.getService(this ,
147: ConditionService.class, null);
148: uidService = (UIDService) sb.getService(this ,
149: UIDService.class, null);
150: metricsService = (MetricsService) sb.getService(this ,
151: MetricsService.class, null);
152: variableEvaluator = new StandardVariableEvaluator(sb);
153:
154: conditionService.addListener(conditionListener);
155: operatingModeService.addListener(operatingModeListener);
156: playbookService.addListener(playbookListener);
157:
158: helper = new PlayHelper(logger, operatingModeService,
159: conditionService, blackboard, uidService, smMap);
160: return true;
161: }
162: return false;
163: }
164:
165: /**
166: * Cleanup before we stop -- release all services.
167: **/
168: public void stop() {
169: ServiceBroker sb = getServiceBroker();
170: if (playbookService != null) {
171: playbookService.removeListener(playbookListener);
172: sb.releaseService(this , PlaybookReadService.class,
173: playbookService);
174: playbookService = null;
175: }
176: if (conditionService != null) {
177: conditionService.removeListener(conditionListener);
178: sb.releaseService(this , ConditionService.class,
179: conditionService);
180: conditionService = null;
181: }
182: if (operatingModeService != null) {
183: sb.releaseService(this , OperatingModeService.class,
184: operatingModeService);
185: operatingModeService = null;
186: }
187: super .stop();
188: }
189:
190: /**
191: * Setup subscriptions to listen for playbook and condition changes.
192: * The current implementation responds immedicately to changes. An
193: * alternative would be to introduce delays before responding to
194: * reduce chaotic behavior.
195: **/
196: public void setupSubscriptions() {
197: Iterator iter = getParameters().iterator();
198: if (iter.hasNext()) {
199: String param = (String) iter.next();
200: try {
201: long missingConditionDelay = Long.parseLong(param);
202: if (missingConditionDelay >= 5000L) {
203: missingConditionTime = System.currentTimeMillis()
204: + missingConditionDelay;
205: } else {
206: logger
207: .error("Bogus missing condition delay is less than 5000");
208: }
209: } catch (Exception e) {
210: logger
211: .error("Error parsing missing condition delay",
212: e);
213: }
214: }
215: playbookListenerSubscription = blackboard
216: .subscribe(playbookListener);
217: conditionListenerSubscription = blackboard
218: .subscribe(conditionListener);
219: operatingModeListenerSubscription = blackboard
220: .subscribe(operatingModeListener);
221: blackboard.publishAdd(conditionListener);
222: blackboard.publishAdd(playbookListener);
223: }
224:
225: /**
226: * The normal plugin execute. Wakes up whenever the playbook is
227: * changed or whenever a Condition is changed. Also wakes up if the
228: * base class has set a timer waiting for all services to be
229: * acquired. If the playbook has changed we refetch the new set of
230: * plays and fetch the conditions required by those new plays. If
231: * the playbook has not changed, but the conditions have, we refetch
232: * the required conditions. If all required conditions are
233: * available, the operating modes are updated from the current
234: * plays.
235: **/
236: public synchronized void execute() {
237: boolean debug = logger.isDebugEnabled();
238: if (debug) {
239: if (conditionListenerSubscription.hasChanged())
240: logger.debug("Condition changed");
241: if (operatingModeListenerSubscription.hasChanged())
242: logger.debug("OperatingMode changed");
243: if (playbookListenerSubscription.hasChanged())
244: logger.debug("Playbook changed");
245: }
246: if (haveServices()) {
247: if (plays == null
248: || playbookListenerSubscription.hasChanged()) {
249: plays = playbookService.getCurrentPlays();
250: if (debug)
251: logger.debug("got " + plays.length + " plays");
252: if (debug)
253: logger.debug("getting conditions");
254: getConditions();
255: } else if (conditionListenerSubscription.hasChanged()) {
256: getConditions();
257: if (debug)
258: logger.debug("got " + smMap.size() + " conditions");
259: } else if (operatingModeListenerSubscription.hasChanged()) {
260: if (debug)
261: logger.debug("operating mode subscription changed");
262: } else if (timerExpired()) {
263: if (debug)
264: logger.debug("missing condition timer expired");
265: } else {
266: if (debug)
267: logger.debug("nothing changed");
268: }
269: if (debug)
270: logger.debug("updateOperatingModes");
271: updateOperatingModes();
272: if (missingConditions.size() > 0 && logger.isWarnEnabled()) {
273: long timeLeft = missingConditionTime
274: - System.currentTimeMillis();
275: if (timeLeft <= 0L) {
276: for (Iterator i = missingConditions.iterator(); i
277: .hasNext();) {
278: String msg = (String) i.next();
279: logger.warn(msg);
280: }
281: missingConditions.clear();
282: } else {
283: cancelTimer();
284: resetTimer(timeLeft);
285: }
286: }
287: }
288: }
289:
290: /**
291: * Scan the current plays for required conditions and stash them in
292: * smMap for use in running the plays. Non-existent conditions that
293: * look like measurements available from the MetricsService are
294: * converted to a MetricsCondition. The name of such a condition is:
295: * Metrics:{<type>:}{<scope>:}<metrics formula name>. Allowed types
296: * are: double, long, integer, string, and boolean. If the type is
297: * omitted, "double" is assumed. The type should match the context
298: * in which the condition is being used. If the scope is omitted, it
299: * is assumed to be this agent. Otherwise, scopes conform to the
300: * Metrics path specification with the following enhancement: The
301: * arglist of a scope can itself be a scope. This is interpreted to
302: * mean the scope containing the inner scope. E.g.: Node(Agent(foo))
303: * is the scope in the node of the agent named foo.
304: **/
305: private void getConditions() {
306: smMap.clear();
307: for (int i = 0; i < plays.length; i++) {
308: Play play = plays[i];
309: for (Iterator x = play.getIfClause().iterator(); x
310: .hasNext();) {
311: Object o = x.next();
312: if (o instanceof String) {
313: String name = (String) o;
314: if (!smMap.containsKey(name)) {
315: Condition sm = conditionService
316: .getConditionByName(name);
317: if (sm == null) {
318: if (name
319: .startsWith(MetricsCondition.METRICS_PREFIX)) {
320: try {
321: sm = MetricsCondition.create(name,
322: metricsService,
323: variableEvaluator);
324: } catch (Exception e) {
325: if (logger.isWarnEnabled())
326: logger.warn(e.getMessage(), e);
327: }
328: } else {
329: if (logger.isInfoEnabled())
330: logger.info("No condition named "
331: + name);
332: }
333: }
334: if (sm != null) {
335: smMap.put(name, sm);
336: }
337: }
338: }
339: }
340: }
341: }
342:
343: /**
344: * Update all operating modes based on conditions and the playbook.
345: * The real work is done in the {@link PlayHelper}.
346: **/
347: private void updateOperatingModes() {
348: tempROMCMap.putAll(romcMap);
349: missingConditions.clear();
350: helper.updateOperatingModes(plays, romcMap, romcChanges,
351: missingConditions);
352: for (Iterator i = romcChanges.iterator(); i.hasNext();) {
353: String operatingModeName = (String) i.next();
354: if (romcMap.containsKey(operatingModeName)) {
355: // Now present. Was it added or changed
356: if (tempROMCMap.containsKey(operatingModeName)) {
357: // Was previously present so must have changed
358: blackboard.publishChange(romcMap
359: .get(operatingModeName));
360: } else {
361: // Was not previously present so must have been added
362: blackboard.publishAdd(romcMap
363: .get(operatingModeName));
364: }
365: } else {
366: // No longer present. Must have been removed.
367: blackboard.publishRemove(tempROMCMap
368: .get(operatingModeName));
369: }
370: }
371: tempROMCMap.clear();
372: }
373: }
|