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: import java.util.StringTokenizer;
037:
038: import org.cougaar.core.mts.MessageAddress;
039: import org.cougaar.core.service.BlackboardService;
040: import org.cougaar.core.service.ConditionService;
041: import org.cougaar.core.service.LoggingService;
042: import org.cougaar.core.service.OperatingModeService;
043: import org.cougaar.core.service.UIDService;
044: import org.cougaar.multicast.AttributeBasedAddress;
045:
046: /**
047: * Helper class for computing OperatingModes from plays and
048: * Conditions
049: **/
050: public class PlayHelper {
051: public static final String AGENT_PREFIX = "agent.";
052: public static final String ATTRIBUTE_PREFIX = "attribute.";
053:
054: private static class OMMapEntry {
055: public OMCRangeList newValue;
056: public List plays = new ArrayList();
057:
058: public OMMapEntry(OMCRangeList nv, Play firstPlay) {
059: newValue = nv;
060: plays.add(firstPlay);
061: }
062: }
063:
064: private Map omMap = new HashMap();
065: private Set iaompUpdates = new HashSet();
066: private Set iaompRemoves = new HashSet();
067: private LoggingService logger;
068: private OperatingModeService operatingModeService;
069: private BlackboardService blackboard;
070: private UIDService uidService;
071: private Map smMap;
072:
073: public PlayHelper(LoggingService ls, OperatingModeService oms,
074: ConditionService sms, BlackboardService bb, UIDService us,
075: Map smm) {
076: logger = ls;
077: operatingModeService = oms;
078: blackboard = bb;
079: uidService = us;
080: smMap = smm;
081: }
082:
083: private static class IAOMPInfo {
084: public String targetName;
085: public String remoteName;
086:
087: public IAOMPInfo(String t, String r) {
088: targetName = t;
089: remoteName = r;
090: }
091:
092: // targetName says it is of type Agent or Attribute, or we
093: // assume the whole thing is an Agent name
094: public MessageAddress getTargetAddress() {
095: if (targetName.substring(0, AGENT_PREFIX.length())
096: .equalsIgnoreCase(AGENT_PREFIX)) {
097: return MessageAddress.getMessageAddress(targetName
098: .substring(AGENT_PREFIX.length()));
099: }
100:
101: if (targetName.substring(0, ATTRIBUTE_PREFIX.length())
102: .equalsIgnoreCase(ATTRIBUTE_PREFIX)) {
103: StringTokenizer tokens = new StringTokenizer(targetName
104: .substring(ATTRIBUTE_PREFIX.length()), ".");
105: try {
106: String community = tokens.nextToken();
107: String attribute = tokens.nextToken();
108: String value = tokens.nextToken();
109: return AttributeBasedAddress
110: .getAttributeBasedAddress(community,
111: attribute, value);
112: } catch (Exception e) {
113: // Could throw a NoSuchElementException from the call to nextToken()
114: // since we dont check hasNextToken.
115: throw new RuntimeException(
116: "Malformed ABA target address specification. Format should be 'attribute.CommunityName.AttributeName.AttributeValue': "
117: + targetName);
118: }
119: }
120:
121: // Default: Neither Agent or Attribute specified. Assume whole thing is agent name.
122: return MessageAddress.getMessageAddress(targetName);
123: }
124: }
125:
126: private IAOMPInfo getIAOMPInfo(String operatingModeName) {
127: if (operatingModeName.startsWith("[")) {
128: int pos = operatingModeName.indexOf("]");
129: if (pos > 0) {
130: String targetName = operatingModeName.substring(1, pos);
131: if (targetName.startsWith(AGENT_PREFIX)
132: || targetName.startsWith(ATTRIBUTE_PREFIX)) {
133: String remoteName = operatingModeName
134: .substring(pos + 1);
135: return new IAOMPInfo(targetName, remoteName);
136: }
137: }
138: }
139: return null;
140: }
141:
142: /**
143: * Update all operating modes based on conditions and the playbook.
144: * This is the real workhorse of the adaptivity engine and carries
145: * out playbook-based adaptivity. All the active plays from the
146: * playbook are considered. If the ifClause evaluates to true, then
147: * the operating mode values are saved in a Map under the operating
148: * mode name. When multiple plays affect the same operating mode,
149: * the values are combined by intersecting the allowed value ranges.
150: * If a play specifies a constraint that would have the effect of
151: * eliminating all possible values for an operating mode, that
152: * constraint is logged and ignored. Finally, the operating modes
153: * are set to the effective value of the combined constraints.
154: * <p>Some operating modes may be remote (in different agents). Such
155: * remote OperatingModes are designated with a naming convention
156: * wherein the remote location is designated with square brackets,
157: * e.g. [agent.3ID]<remotename>. This notation may also be used with
158: * attribute-based addresses using the notation:
159: * [attribute.<community>.<attribute_name>.<attribute_value>]. The
160: * caller supplies a Map of such remote modes and we use or update
161: * that Map accordingly. The names of remote operating modes that
162: * are added or removed are returned.
163: * @param plays the plays to be tested and applied.
164: * @param iaompMap a Map of the current remote operating mode
165: * constraints. Items are added or removed from this Map according
166: * to whether or not the given plays specify constraints on those
167: * remote modes.
168: * @param iaompChanges the names of the remote operating mode
169: * constraints that were added or removed from iaompMap. The action
170: * is implied by whether or not the iaompMap has the named iaomp.
171: **/
172: public void updateOperatingModes(Play[] plays, Map iaompMap,
173: Set iaompChanges, List missingConditions) {
174: if (logger.isDebugEnabled())
175: logger.debug("updateOperatingModes " + plays.length
176: + " plays");
177:
178: /* run the plays - that is, do the comparisons in the "If" parts
179: * of the plays and if they evaluate to true, set the operating modes in
180: * the "Then" parts of the plays and publish the new values to the
181: * blackboard.
182: */
183:
184: for (int i = 0; i < plays.length; i++) {
185: Play play = plays[i];
186: try {
187: if (eval(play.getIfClause().iterator())) {
188: if (logger.isDebugEnabled())
189: logger.debug("Using play: " + play);
190: ConstraintPhrase[] playConstraints = play
191: .getOperatingModeConstraints();
192: for (int j = 0; j < playConstraints.length; j++) {
193: ConstraintPhrase cp = playConstraints[j];
194: String operatingModeName = cp.getProxyName();
195: OMCRangeList av = cp.getAllowedValues()
196: .applyOperator(cp.getOperator());
197: OMMapEntry omme = (OMMapEntry) omMap
198: .get(operatingModeName);
199: if (omme == null) {
200: omMap.put(operatingModeName,
201: new OMMapEntry(av, play));
202: } else {
203: OMCRangeList intersection = omme.newValue
204: .intersect(av);
205: if (intersection.isEmpty()) {
206: logger.error("Play conflict for play "
207: + play + " against "
208: + omme.plays);
209: } else {
210: omme.newValue = intersection;
211: omme.plays.add(play);
212: }
213: }
214: }
215: } else {
216: if (logger.isDebugEnabled())
217: logger.debug("Skipping play: " + play);
218: }
219: } catch (Exception iae) {
220: if (logger.isDebugEnabled()) {
221: logger
222: .debug(iae.getMessage() + " in play: "
223: + play, iae);
224: } else if (iae instanceof MissingConditionException) {
225: missingConditions.add(iae.getMessage()
226: + " in play: " + play);
227: } else {
228: logger
229: .error(iae.getMessage() + " in play: "
230: + play);
231: }
232: }
233: }
234: Set operatingModes = new HashSet(operatingModeService
235: .getAllOperatingModeNames());
236: iaompChanges.clear();
237: // post initialized -- iaompUpdates.clear();
238: // post initialized -- iaompRemoves.clear();
239: if (logger.isDebugEnabled())
240: logger.debug("Updating operating modes");
241: for (Iterator i = omMap.entrySet().iterator(); i.hasNext();) {
242: Map.Entry entry = (Map.Entry) i.next();
243: String operatingModeName = (String) entry.getKey();
244: OMMapEntry omme = (OMMapEntry) entry.getValue();
245: IAOMPInfo iaompInfo = getIAOMPInfo(operatingModeName);
246: if (iaompInfo != null) {
247: // A remote operating mode. Create or update a policy to constrain it.
248: ConstraintPhrase[] cp = { new ConstraintPhrase(
249: iaompInfo.remoteName, ConstraintOperator.IN,
250: omme.newValue) };
251: PolicyKernel pk = new PolicyKernel(
252: ConstrainingClause.TRUE_CLAUSE, cp);
253: InterAgentOperatingModePolicy iaomp = (InterAgentOperatingModePolicy) iaompMap
254: .get(operatingModeName);
255: if (iaomp == null) {
256: iaomp = new PlayHelperInterAgentOperatingModePolicy(
257: pk);
258: iaomp.setUID(uidService.nextUID());
259: iaomp.setTarget(iaompInfo.getTargetAddress());
260: iaompMap.put(operatingModeName, iaomp);
261: iaompChanges.add(operatingModeName);
262: } else {
263: PolicyKernel old = iaomp.getPolicyKernel();
264: if (!old.equals(pk)) {
265: iaomp.setPolicyKernel(pk);
266: iaompChanges.add(operatingModeName);
267: }
268: }
269: iaompUpdates.add(operatingModeName);
270: } else {
271: Comparable value = omme.newValue.getEffectiveValue();
272: OperatingMode om = operatingModeService
273: .getOperatingModeByName(operatingModeName);
274: if (om == null) {
275: if (logger.isDebugEnabled())
276: logger.debug("OperatingMode not present: "
277: + operatingModeName);
278: } else {
279: Comparable oldValue = om.getValue();
280: Class omValueClass = oldValue.getClass();
281: if (omValueClass != value.getClass())
282: value = coerceValue(value, omValueClass);
283: if (!value.equals(oldValue)) {
284: if (logger.isInfoEnabled())
285: logger.info("Setting OperatingMode "
286: + operatingModeName + " to "
287: + value);
288: try {
289: om.setValue(value);
290: blackboard.publishChange(om);
291: } catch (IllegalArgumentException iae) {
292: if (logger.isErrorEnabled()) {
293: logger.error(iae.getMessage(), iae);
294: for (Iterator iter = omme.plays
295: .iterator(); iter.hasNext();) {
296: logger
297: .error("Play of previous error: "
298: + iter.next()
299: .toString());
300: }
301: }
302: }
303: }
304: }
305: operatingModes.remove(operatingModeName); // This one has been accounted for
306: }
307: }
308: if (!operatingModes.isEmpty() && logger.isDebugEnabled()) {
309: for (Iterator i = operatingModes.iterator(); i.hasNext();) {
310: logger.debug("No play found to set operating mode: "
311: + i.next());
312: }
313: }
314: omMap.clear();
315:
316: // We need to remove all the items from iaompMap that were not
317: // changed or added above. The adds are named in iaompChanges and
318: // the changes are in iaompUpdates.
319: iaompRemoves.addAll(iaompMap.keySet());
320: iaompRemoves.removeAll(iaompUpdates);
321: iaompRemoves.removeAll(iaompChanges);
322: iaompMap.keySet().removeAll(iaompRemoves);
323: // Finally, add all the removes to iaompChanges
324: iaompChanges.addAll(iaompRemoves);
325: iaompUpdates.clear();
326: iaompRemoves.clear();
327: }
328:
329: private Comparable coerceValue(Comparable value, Class toClass) {
330: if (toClass == String.class)
331: return value.toString();
332: if (value instanceof Number
333: && Number.class.isAssignableFrom(toClass)) {
334: Number n = (Number) value;
335: if (toClass == Double.class)
336: return new Double(n.doubleValue());
337: if (toClass == Long.class)
338: return new Long(n.longValue());
339: if (toClass == Integer.class)
340: return new Integer(n.intValue());
341: }
342: return value; // Can't be coerced
343: }
344:
345: /**
346: * Evaluate an if clause.
347: **/
348: private boolean eval(Iterator x) {
349: if (!x.hasNext())
350: throw new IllegalArgumentException("Incomplete play");
351: Object o = x.next();
352: if (o.equals(BooleanOperator.NOT)) {
353: return !eval(x);
354: }
355: if (o.equals(BooleanOperator.AND)) {
356: return eval(x) & eval(x);
357: }
358: if (o.equals(BooleanOperator.OR)) {
359: return eval(x) | eval(x);
360: }
361: if (o.equals(BooleanOperator.TRUE)) {
362: return true;
363: }
364: if (o.equals(BooleanOperator.FALSE)) {
365: return false;
366: }
367: if (o instanceof ConstraintOpValue) {
368: ConstraintOpValue phrase = (ConstraintOpValue) o;
369: Comparable paramValue = phrase.getValue();
370: OMCRangeList paramRanges = phrase.getAllowedValues();
371: ConstraintOperator op = phrase.getOperator();
372: Comparable conditionValue = evalArithmetic(x);
373: if (op.equals(ConstraintOperator.IN)
374: || op.equals(ConstraintOperator.NOTIN)) {
375: boolean isIn = paramRanges.isAllowed(conditionValue);
376: if (op.equals(ConstraintOperator.IN))
377: return isIn;
378: return !isIn;
379: }
380: int diff = conditionValue.compareTo(paramValue);
381: if (op.equals(ConstraintOperator.GREATERTHAN))
382: return diff > 0;
383: if (op.equals(ConstraintOperator.GREATERTHANOREQUAL))
384: return diff >= 0;
385: if (op.equals(ConstraintOperator.LESSTHAN))
386: return diff < 0;
387: if (op.equals(ConstraintOperator.LESSTHANOREQUAL))
388: return diff <= 0;
389: if (op.equals(ConstraintOperator.EQUAL))
390: return diff == 0;
391: if (op.equals(ConstraintOperator.NOTEQUAL))
392: return diff != 0;
393: throw new IllegalArgumentException(
394: "invalid ConstraintOperator " + op);
395: }
396: throw new IllegalArgumentException("invalid if clause " + o);
397: }
398:
399: private Comparable evalArithmetic(Iterator x) {
400: if (!x.hasNext())
401: throw new IllegalArgumentException("Incomplete play");
402: Object o = x.next();
403: if (o instanceof ArithmeticOperator) {
404: ArithmeticOperator op = (ArithmeticOperator) o;
405: if (op.equals(ArithmeticOperator.ADD)) {
406: Comparable r = evalArithmetic(x);
407: Comparable l = evalArithmetic(x);
408: return ComparableHelper.add(l, r);
409: }
410: if (op.equals(ArithmeticOperator.SUBTRACT)) {
411: Comparable r = evalArithmetic(x);
412: Comparable l = evalArithmetic(x);
413: return ComparableHelper.subtract(l, r);
414: }
415: if (op.equals(ArithmeticOperator.MULTIPLY)) {
416: Comparable r = evalArithmetic(x);
417: Comparable l = evalArithmetic(x);
418: return ComparableHelper.multiply(l, r);
419: }
420: if (op.equals(ArithmeticOperator.DIVIDE)) {
421: Comparable r = evalArithmetic(x);
422: Comparable l = evalArithmetic(x);
423: return ComparableHelper.divide(l, r);
424: }
425: if (op.equals(ArithmeticOperator.NEGATE)) {
426: Comparable r = evalArithmetic(x);
427: return ComparableHelper.negate(r);
428: }
429: throw new IllegalArgumentException(
430: "Unknown ArithmeticOperator: " + op);
431: }
432: if (o instanceof String) {
433: String conditionName = (String) o;
434: Condition condition = (Condition) smMap.get(conditionName);
435: if (condition == null) {
436: OMMapEntry omme = (OMMapEntry) omMap.get(conditionName);
437: if (omme != null) {
438: return omme.newValue.getEffectiveValue();
439: }
440: throw new MissingConditionException(
441: "No Condition named " + conditionName);
442: }
443: return condition.getValue();
444: }
445: throw new IllegalArgumentException("invalid if clause " + o);
446: }
447:
448: // Don't persist internally generated iaomp
449: private static class PlayHelperInterAgentOperatingModePolicy extends
450: InterAgentOperatingModePolicy {
451:
452: // Constructors
453: public PlayHelperInterAgentOperatingModePolicy(PolicyKernel pk) {
454: super (pk);
455: }
456:
457: public boolean isPersistable() {
458: return false;
459: }
460: }
461: }
|