001: /*
002: * <copyright>
003: *
004: * Copyright 2001-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.planning.plugin.completion;
028:
029: import java.io.IOException;
030: import java.io.PrintWriter;
031: import java.lang.reflect.Constructor;
032: import java.util.Collection;
033: import java.util.Collections;
034: import java.util.Comparator;
035: import java.util.Iterator;
036: import java.util.Set;
037: import java.util.SortedSet;
038: import java.util.TreeSet;
039:
040: import javax.servlet.http.HttpServlet;
041: import javax.servlet.http.HttpServletRequest;
042: import javax.servlet.http.HttpServletResponse;
043:
044: import org.cougaar.core.mts.MessageAddress;
045: import org.cougaar.core.service.EventService;
046: import org.cougaar.core.service.ServletService;
047: import org.cougaar.core.service.wp.WhitePagesService;
048: import org.cougaar.core.wp.ListAllNodes;
049:
050: /**
051: * This plugin gathers and integrates completion information from
052: * nodes in a society to determine the "completion" of the current
053: * tasks. This plugin should be included only in one agent at the root
054: * of the society such as NCA. When the root determines that
055: * completion has been acheived (or is never going to be achieved), it
056: * advances the clock with the expectation that the advancement will
057: * engender additional activity and waits for the completion of that
058: * work.
059: **/
060:
061: public abstract class CompletionSocietyPlugin extends
062: CompletionSourcePlugin {
063: /** Length of inactive period required before time is advanced **/
064: private static final long DEFAULT_NORMAL_TIME_ADVANCE_DELAY = 5000L;
065: /** Length of inactive period required before time is advanced for the first time **/
066: private static final long DEFAULT_INITIAL_TIME_ADVANCE_DELAY = 150000L;
067: /** Length of time after time is advanced before another advance is allowed. **/
068: private static final long DEFAULT_ADVANCE_TIME_ADVANCE_DELAY = 20000L;
069: /** How much to advance time **/
070: private static final long DEFAULT_TIME_STEP = 86400000L;
071: private static final Class[] stringArgType = { String.class };
072:
073: private long NORMAL_TIME_ADVANCE_DELAY = DEFAULT_NORMAL_TIME_ADVANCE_DELAY;
074: private long INITIAL_TIME_ADVANCE_DELAY = DEFAULT_INITIAL_TIME_ADVANCE_DELAY;
075: private long ADVANCE_TIME_ADVANCE_DELAY = DEFAULT_ADVANCE_TIME_ADVANCE_DELAY;
076:
077: private static final String NORMAL_TIME_ADVANCE_DELAY_KEY = "NORMAL_TIME_ADVANCE_DELAY=";
078: private static final String INITIAL_TIME_ADVANCE_DELAY_KEY = "INITIAL_TIME_ADVANCE_DELAY=";
079: private static final String ADVANCE_TIME_ADVANCE_DELAY_KEY = "ADVANCE_TIME_ADVANCE_DELAY=";
080: private static final String DO_PERSISTENCE_KEY = "DO_PERSISTENCE=";
081: private boolean DO_PERSISTENCE = true;
082: private long TIME_STEP = DEFAULT_TIME_STEP;
083: private int advancementSteps = 0;
084: private int refreshInterval = 0;
085: private boolean persistenceNeeded = false;
086: private static final Class[] requiredServices = {
087: EventService.class, ServletService.class,
088: WhitePagesService.class };
089:
090: /**
091: * Subclasses should provide an array of CompletionActions to be
092: * handled. Each action will be invoked whenever the laggard
093: * status changes until the action returns true. After an action
094: * returns true, the next action is invoked until it returns true.
095: * This continues until all completion actions have returned true.
096: * After that, the standard time-advance action is invoked.
097: **/
098: public interface CompletionAction {
099: boolean checkCompletion(boolean haveLaggard);
100: }
101:
102: private long timeToAdvanceTime = Long.MIN_VALUE; // Time to advance time (maybe)
103: private long tomorrow = Long.MIN_VALUE; // Demo time must
104: // exceed this before
105: // time step
106: private CompletionAction[] completionActions = null;
107:
108: private int nextCompletionAction = 0;
109:
110: private boolean isComplete = false;
111:
112: private LaggardFilter filter = new LaggardFilter();
113:
114: private EventService eventService = null;
115: private WhitePagesService wps = null;
116: private ServletService servletService = null;
117:
118: public CompletionSocietyPlugin() {
119: super (requiredServices);
120: }
121:
122: public void setupSubscriptions() {
123: Collection params = getParameters();
124: for (Iterator i = params.iterator(); i.hasNext();) {
125: String param = (String) i.next();
126: if (param.startsWith(ADVANCE_TIME_ADVANCE_DELAY_KEY)) {
127: ADVANCE_TIME_ADVANCE_DELAY = Long.parseLong(param
128: .substring(ADVANCE_TIME_ADVANCE_DELAY_KEY
129: .length()));
130: if (logger.isInfoEnabled())
131: logger.info("Set " + ADVANCE_TIME_ADVANCE_DELAY_KEY
132: + ADVANCE_TIME_ADVANCE_DELAY);
133: continue;
134: }
135: if (param.startsWith(NORMAL_TIME_ADVANCE_DELAY_KEY)) {
136: NORMAL_TIME_ADVANCE_DELAY = Long.parseLong(param
137: .substring(NORMAL_TIME_ADVANCE_DELAY_KEY
138: .length()));
139: if (logger.isInfoEnabled())
140: logger.info("Set " + NORMAL_TIME_ADVANCE_DELAY_KEY
141: + NORMAL_TIME_ADVANCE_DELAY);
142: continue;
143: }
144: if (param.startsWith(INITIAL_TIME_ADVANCE_DELAY_KEY)) {
145: INITIAL_TIME_ADVANCE_DELAY = Long.parseLong(param
146: .substring(INITIAL_TIME_ADVANCE_DELAY_KEY
147: .length()));
148: if (logger.isInfoEnabled())
149: logger.info("Set " + INITIAL_TIME_ADVANCE_DELAY_KEY
150: + INITIAL_TIME_ADVANCE_DELAY);
151: continue;
152: }
153: if (param.startsWith(DO_PERSISTENCE_KEY)) {
154: DO_PERSISTENCE = !param.substring(
155: DO_PERSISTENCE_KEY.length()).equalsIgnoreCase(
156: "false");
157: if (logger.isInfoEnabled())
158: logger.info("Set " + DO_PERSISTENCE_KEY
159: + DO_PERSISTENCE);
160: continue;
161: }
162: }
163: super .setupSubscriptions();
164: }
165:
166: protected boolean haveServices() {
167: if (servletService != null && wps != null)
168: return true;
169: if (super .haveServices()) {
170: eventService = (EventService) getServiceBroker()
171: .getService(this , EventService.class, null);
172: wps = (WhitePagesService) getServiceBroker().getService(
173: this , WhitePagesService.class, null);
174: servletService = (ServletService) getServiceBroker()
175: .getService(this , ServletService.class, null);
176: try {
177: servletService.register("/completionControl",
178: new CompletionControlServlet());
179: } catch (Exception e) {
180: logger.error(
181: "Failed to register completionControl servlet",
182: e);
183: }
184: return true;
185: }
186: return false;
187: }
188:
189: protected abstract CompletionAction[] getCompletionActions();
190:
191: private static final Comparator messageAddressComparator = new Comparator() {
192: public int compare(Object o1, Object o2) {
193: MessageAddress a1 = (MessageAddress) o1;
194: MessageAddress a2 = (MessageAddress) o2;
195: return a1.getAddress().compareTo(a2.getAddress());
196: }
197: };
198:
199: protected Set getTargets() {
200: try {
201: Set names = ListAllNodes.listAllNodes(wps); // not scalable!
202: Set targets = new TreeSet(messageAddressComparator);
203: for (Iterator i = names.iterator(); i.hasNext();) {
204: targets.add(MessageAddress.getMessageAddress((String) i
205: .next()));
206: }
207: return targets;
208: } catch (Exception e) {
209: if (logger.isErrorEnabled()) {
210: logger.error("Unable to list all node names", e);
211: }
212: return Collections.EMPTY_SET;
213: }
214: }
215:
216: private void insureCompletionActions() {
217: if (completionActions == null) {
218: completionActions = getCompletionActions();
219: }
220: }
221:
222: protected boolean adjustLaggards(SortedSet laggards) {
223: return false;
224: }
225:
226: protected void handleNewLaggard(Laggard worstLaggard) {
227: boolean haveLaggard = worstLaggard != null
228: && worstLaggard.isLaggard();
229: if (logger.isInfoEnabled() && filter.filter(worstLaggard)) {
230: logger.info("new worst " + worstLaggard);
231: }
232: insureCompletionActions();
233: if (nextCompletionAction < completionActions.length) {
234: try {
235: if (completionActions[nextCompletionAction]
236: .checkCompletion(haveLaggard)) {
237: if (logger.isInfoEnabled()) {
238: logger
239: .info("Completed "
240: + completionActions[nextCompletionAction]);
241: }
242: nextCompletionAction++;
243: }
244: } catch (Exception e) {
245: logger.error("Error executing completion action "
246: + completionActions[nextCompletionAction], e);
247: nextCompletionAction++;
248: }
249: } else {
250: checkTimeAdvance(haveLaggard);
251: }
252: }
253:
254: /**
255: * Check if conditions are right for advancing time. There must have
256: * been no laggards for TIME_ADVANCE_DELAY milliseconds. Anytime we
257: * have a laggard, the clock starts over. The clock is also reset
258: * after advancing time to avoid advancing a second time before the
259: * agents have had an opportunity to become laggards due to the
260: * first time advance.
261: **/
262: protected void checkTimeAdvance(boolean haveLaggard) {
263: long demoNow = alarmService.currentTimeMillis();
264: if (tomorrow == Long.MIN_VALUE) {
265: tomorrow = (demoNow / TIME_STEP) * TIME_STEP; // Beginning of today
266: timeToAdvanceTime = now + INITIAL_TIME_ADVANCE_DELAY;
267: }
268:
269: if (haveLaggard) {
270: timeToAdvanceTime = Math.max(timeToAdvanceTime, now
271: + NORMAL_TIME_ADVANCE_DELAY);
272: } else {
273: long timeToGo = Math.max(timeToAdvanceTime - now, tomorrow
274: + NORMAL_TIME_ADVANCE_DELAY - demoNow);
275: if (timeToGo > 0) {
276: if (logger.isDebugEnabled())
277: logger.debug(timeToGo + " left");
278: } else {
279: if (advancementSteps <= 0) {
280: if (persistenceNeeded) {
281: if (DO_PERSISTENCE) {
282: setPersistenceNeeded();
283: }
284: persistenceNeeded = false;
285: }
286: if (!isComplete) {
287: if (eventService.isEventEnabled()) {
288: eventService.event("Planning Complete");
289: }
290: isComplete = true;
291: }
292: return;
293: }
294: long newTomorrow = ((demoNow / TIME_STEP) + 1)
295: * TIME_STEP;
296: advancementSteps -= (int) ((newTomorrow - tomorrow) / TIME_STEP);
297: tomorrow = newTomorrow;
298: persistenceNeeded = true;
299: try {
300: demoControlService.setSocietyTime(tomorrow, true);
301: if (logger.isInfoEnabled()) {
302: logger.info("Advance time from "
303: + formatDate(demoNow) + " to "
304: + formatDate(tomorrow));
305: }
306: } catch (RuntimeException re) {
307: System.err.println(formatDate(now) + ": Exception("
308: + re.getMessage()
309: + ") trying to advance time from "
310: + formatDate(demoNow) + " to "
311: + formatDate(tomorrow));
312: }
313: timeToAdvanceTime = now + ADVANCE_TIME_ADVANCE_DELAY;
314: }
315: }
316: if (isComplete) {
317: if (eventService.isEventEnabled()) {
318: eventService.event("Planning Active");
319: }
320: isComplete = false;
321: }
322: }
323:
324: private class CompletionControlServlet extends HttpServlet {
325: protected void doGet(HttpServletRequest request,
326: HttpServletResponse response) throws IOException {
327: doPostOrGet(request, response, false);
328: }
329:
330: protected void doPost(HttpServletRequest request,
331: HttpServletResponse response) throws IOException {
332: doPostOrGet(request, response, true);
333: }
334:
335: protected void doPostOrGet(HttpServletRequest request,
336: HttpServletResponse response, boolean doUpdate)
337: throws IOException {
338: if (!"Submit".equals(request.getParameter("submit"))) {
339: doUpdate = false;
340: }
341: try {
342: refreshInterval = Integer.parseInt(request
343: .getParameter("refreshInterval"));
344: } catch (Exception e) {
345: }
346: response.setContentType("text/html");
347: if (refreshInterval > 0) {
348: response.setHeader("Refresh", String
349: .valueOf(refreshInterval));
350: }
351: PrintWriter out = response.getWriter();
352: insureCompletionActions();
353: out
354: .println("<html>\n <head>\n <title>Completion Control</title>\n </head>");
355: out
356: .println(" <body>\n <h1>Completion Control Servlet</h1>"
357: + " <form action=\"completionControl\" method=\"post\">");
358: out.println(" <table>");
359: out.println(" <tr><td>Scenario Time</td><td>"
360: + formatDate(alarmService.currentTimeMillis())
361: + "</td></tr>");
362: INITIAL_TIME_ADVANCE_DELAY = handleField(
363: "initialDelay",
364: "Initial Time Advance Delay",
365: "The duration of the quiet period required before the first time advance",
366: new Long(INITIAL_TIME_ADVANCE_DELAY), request, out,
367: doUpdate).longValue();
368: NORMAL_TIME_ADVANCE_DELAY = handleField(
369: "normalDelay",
370: "Normal Time Advance Delay",
371: "The duration of the quiet period required before the normal time advances",
372: new Long(NORMAL_TIME_ADVANCE_DELAY), request, out,
373: doUpdate).longValue();
374: ADVANCE_TIME_ADVANCE_DELAY = handleField(
375: "advanceDelay",
376: "Advance Time Delay",
377: "The delay after a time advance before the next time advance is allowed",
378: new Long(ADVANCE_TIME_ADVANCE_DELAY), request, out,
379: doUpdate).longValue();
380: TIME_STEP = handleField("timeStep", "Advance Time Step",
381: "The size of the time step for each advancement",
382: new Long(TIME_STEP), request, out, doUpdate)
383: .longValue();
384: advancementSteps = handleField(
385: "nSteps",
386: "Number of Time Steps to Advance",
387: "Specifies how many steps of advancement should be performed",
388: new Integer(advancementSteps), request, out,
389: doUpdate).intValue();
390: for (int i = 0; i < completionActions.length; i++) {
391: out.println(" <tr><td>"
392: + completionActions[i].toString()
393: + "</td><td>"
394: + ((i < nextCompletionAction) ? "Completed"
395: : "Pending") + "</td></tr>");
396: }
397: handleField(
398: "refreshInterval",
399: "Automatic Refresh Interval (seconds)",
400: "Specifies interval between automatic refresh of this screen",
401: new Integer(refreshInterval), request, out, false);
402: out.println(" </table>");
403: out
404: .println("<input type=\"submit\" name=\"submit\" value=\"Submit\">");
405: out
406: .println("<input type=\"submit\" name=\"submit\" value=\"Refresh\">");
407: out.println(" </form>\n </body>\n</html>");
408: }
409:
410: private Number handleField(String name, String label,
411: String description, Number currentValue,
412: HttpServletRequest request, PrintWriter out,
413: boolean doUpdate) {
414: Number newValue = createValue(currentValue, request
415: .getParameter(name));
416: if (doUpdate && newValue != null
417: && !newValue.equals(currentValue)) {
418: currentValue = newValue;
419: }
420: out.println(" <tr><td>" + label + "</td><td>"
421: + "<input name=\"" + name
422: + "\" type=\"text\" value=\""
423: + currentValue.toString() + "\"></td></tr>");
424: return currentValue;
425: }
426:
427: private Number createValue(Number currentValue, String v) {
428: try {
429: Constructor constructor = currentValue.getClass()
430: .getConstructor(stringArgType);
431: Object[] args = { v };
432: return (Number) constructor.newInstance(args);
433: } catch (Exception e) {
434: return currentValue;
435: }
436: }
437: }
438: }
|