001: /*
002: * <copyright>
003: *
004: * Copyright 2003-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.lib.aggagent.session;
028:
029: import java.io.PrintWriter;
030: import java.io.StringWriter;
031: import java.util.Iterator;
032: import java.util.LinkedList;
033: import java.util.List;
034:
035: import org.cougaar.lib.aggagent.query.CompoundKey;
036: import org.cougaar.lib.aggagent.query.ResultSetDataAtom;
037: import org.cougaar.lib.aggagent.util.InverseSax;
038: import org.cougaar.lib.aggagent.util.XmlUtils;
039: import org.w3c.dom.Element;
040: import org.w3c.dom.NodeList;
041:
042: /**
043: * The UpdateDelta class represents the communication strategy used for
044: * transfering data between the society agents and an aggregation agent.
045: * The typical usage of this class is as a container for ResultSetDataAtoms,
046: * which are loaded into the UpdateDelta by a society agent. The delta is
047: * then transfered as XML to the aggregation agent, where it is reconstituted
048: * and applied to the appropriate result set.
049: * <br><br>
050: * An alternate usage is as a container for more generic XmlTransferable
051: * objects such as queries and result sets. Though such an UpdateDelta cannot
052: * (at this time) be reconstituted automatically, this strategy is used in
053: * communications between UI clients and the aggregation agent.
054: * <br><br>
055: * An UpdateDelta can be used in either of two modes: increment or
056: * replacement. The former reflects the familiar behavior of the
057: * IncrementalSubscription class (with "added", "changed", and "removed"
058: * lists), while the latter produces a new result set in its entirety. The
059: * mode may be set at any time without losing data, but a good practice is to
060: * set the mode once before any content elements are added.
061: */
062: public class UpdateDelta {
063: private static String AGENT_ID = "agent_id";
064: private static String QUERY_ID = "query_id";
065: private static String SESSION_ID = "session_id";
066: private static String ADDED_TAG = "added";
067: private static String CHANGED_TAG = "changed";
068: private static String REMOVED_TAG = "removed";
069: private static String REPLACEMENT_TAG = "replacement";
070: private static String ERROR_TAG = "error";
071:
072: public static String UPDATE_TAG = "update";
073:
074: // for script error reporting
075: private String errorReport = null;
076:
077: // By default, replacementMode is set to false. The addedList also serves as
078: // the replacementList, and hence should be used in either mode. Both
079: // changedList and removedList are used only when replacementMode is false.
080: private boolean replacementMode = false;
081: private List addedList = new LinkedList();
082: private List changedList = new LinkedList();
083: private List removedList = new LinkedList();
084:
085: private String cougaarAgentId = null;
086: private String queryId = null;
087: private String sessionKey = null;
088:
089: /**
090: * Create a new UpdateDelta, presumably for transport to a remote location.
091: * The three String identifiers indicate which COUGAAR agent, query, and
092: * local session are associated with this UpdateDelta. Content must be
093: * added before this delta will be useful.
094: * <br><br>
095: * <b>Note: The setReplacement() method must be called before content can
096: * safely be added.</b> The boolean argument tells whether to create added,
097: * changed, and removed lists, or simply a replacement list.
098: */
099: public UpdateDelta(String agent, String query, String key) {
100: cougaarAgentId = agent;
101: queryId = query;
102: sessionKey = key;
103: }
104:
105: /**
106: * Create an UpdateDelta from XML representation. Presumably, this is
107: * received from a remote location, complete with identifiers and content
108: * data elements.
109: */
110: public UpdateDelta(Element root) {
111: this (root.getAttribute(AGENT_ID), root.getAttribute(QUERY_ID),
112: root.getAttribute(SESSION_ID));
113:
114: NodeList nl = root.getElementsByTagName(ERROR_TAG);
115: if (nl.getLength() > 0) {
116: errorReport = XmlUtils.getElementText((Element) nl.item(0));
117: } else {
118: nl = root.getElementsByTagName(REPLACEMENT_TAG);
119: if (nl.getLength() > 0) {
120: setReplacement(true);
121: loadAtoms(addedList, nl);
122: } else {
123: setReplacement(false);
124: loadAtoms(addedList, root
125: .getElementsByTagName(ADDED_TAG));
126: loadAtoms(changedList, root
127: .getElementsByTagName(CHANGED_TAG));
128: loadAtoms(removedList, root
129: .getElementsByTagName(REMOVED_TAG));
130: }
131: }
132: }
133:
134: // This operation presumes that elements are ResultSetDataAtoms, which may
135: // not be true. Other implementations of XmlTransferable exist.
136: private static void loadAtoms(List l, NodeList bunches) {
137: if (bunches.getLength() == 0)
138: return;
139:
140: // presume that there is at most one atom list of each variety
141: NodeList atoms = ((Element) bunches.item(0))
142: .getElementsByTagName(ResultSetDataAtom.DATA_ATOM_TAG);
143: for (int i = 0; i < atoms.getLength(); i++)
144: l.add(new ResultSetDataAtom((Element) atoms.item(i)));
145: }
146:
147: public String getAgentId() {
148: return cougaarAgentId;
149: }
150:
151: public String getQueryId() {
152: return queryId;
153: }
154:
155: public List getAddedList() {
156: return addedList;
157: }
158:
159: public List getChangedList() {
160: return changedList;
161: }
162:
163: public List getRemovedList() {
164: return removedList;
165: }
166:
167: public List getReplacementList() {
168: return addedList;
169: }
170:
171: public void clearContents() {
172: addedList.clear();
173: changedList.clear();
174: removedList.clear();
175: }
176:
177: public boolean isErrorReport() {
178: return errorReport != null;
179: }
180:
181: public String getErrorReport() {
182: return errorReport;
183: }
184:
185: public void setErrorReport(Throwable t) {
186: // capture the error as a String
187: StringWriter w = new StringWriter();
188: PrintWriter p = new PrintWriter(w);
189: t.printStackTrace(p);
190: p.flush();
191: errorReport = w.toString();
192:
193: // clear out partial data
194: clearContents();
195: }
196:
197: public boolean isReplacement() {
198: return replacementMode;
199: }
200:
201: /**
202: * Set the "replacement mode" true or false. If true, the UpdateDelta
203: * contains three lists of data elements after the fashion of an
204: * IncrementalSubscription; i.e., added, changed, and removed elements. If
205: * false, the UpdateDelta contains a single list which should be interpreted
206: * as a replacement for all previously collected data elements. This
207: * distinction is only meaningful for persistent queries.
208: */
209: public void setReplacement(boolean b) {
210: replacementMode = b;
211: }
212:
213: /**
214: * Convert this UpdateDelta to an XML format. Depending on whether this is
215: * a replacement or not, this will, respectively, produce a list of elements
216: * intended to replace an existing result set or a list of modifications to
217: * be applied to an existing result set. Data elements must implement the
218: * XmlTransferable interface.
219: */
220: public String toXml() {
221: InverseSax doc = new InverseSax();
222: includeXml(doc);
223: return doc.toString();
224: }
225:
226: public void includeXml(InverseSax doc) {
227: doc.addElement(UPDATE_TAG);
228: doc.addAttribute(SESSION_ID, sessionKey);
229: doc.addAttribute(QUERY_ID, queryId);
230: doc.addAttribute(AGENT_ID, cougaarAgentId);
231: if (isErrorReport()) {
232: doc.addTextElement(ERROR_TAG, errorReport);
233: } else if (isReplacement()) {
234: sendBunch(addedList, REPLACEMENT_TAG, doc);
235: } else {
236: sendBunch(addedList, ADDED_TAG, doc);
237: sendBunch(changedList, CHANGED_TAG, doc);
238: sendBunch(removedList, REMOVED_TAG, doc);
239: }
240: doc.endElement();
241: }
242:
243: private static void sendBunch(List c, String type, InverseSax doc) {
244: if (c != null) {
245: doc.addElement(type);
246: for (Iterator i = c.iterator(); i.hasNext();)
247: ((XmlTransferable) i.next()).includeXml(doc);
248: doc.endElement();
249: }
250: }
251:
252: // - - - - - - - Testing Code - - - - - - - - - - - - - - - - - - - - - - - -
253:
254: private void summarize() {
255: System.out.println("UpdateDelta: (" + cougaarAgentId + ", "
256: + queryId + ", " + sessionKey + ")");
257: System.out.println(" - is errorReport: " + isErrorReport());
258: System.out.println(" - is replacement: " + isReplacement());
259: if (isErrorReport()) {
260: System.out.println("<<");
261: System.out.println(errorReport);
262: System.out.println(">>");
263: } else if (isReplacement()) {
264: summarizeList("replacement", addedList);
265: } else {
266: summarizeList("added", addedList);
267: summarizeList("changed", changedList);
268: summarizeList("removed", removedList);
269: }
270: }
271:
272: private static void summarizeList(String name, List atoms) {
273: System.out.print(" - ");
274: if (atoms == null)
275: System.out.println("no " + name + " list found");
276: else if (atoms.size() == 0)
277: System.out.println(name + " list is empty");
278: else
279: System.out.println(name + " list contains " + atoms.size()
280: + " atom" + (atoms.size() == 1 ? "" : "s"));
281: }
282:
283: public static void main(String[] argv) {
284: testUpdateDelta(getIncrementalDelta());
285: testUpdateDelta(getReplacementDelta());
286: testUpdateDelta(getErrorDelta());
287: }
288:
289: private static void testUpdateDelta(UpdateDelta ud) {
290: System.out.println("<< testing UpdateDelta >>");
291: ud.summarize();
292:
293: System.out.println("Generating XML:");
294: System.out.print(ud.toXml());
295:
296: System.out.println("Parsing XML:");
297: try {
298: UpdateDelta ud2 = new UpdateDelta(XmlUtils
299: .parse(ud.toXml()));
300: ud2.summarize();
301:
302: System.out.println("Regenerating XML:");
303: System.out.print(ud2.toXml());
304: } catch (Exception eek) {
305: System.out.println(" - Failed (" + eek + ")");
306: }
307: System.out.println("<< done >>");
308: }
309:
310: private static int atom_serial_counter = 0;
311: private static List atom_ids = new LinkedList();
312: static {
313: atom_ids.add("serial");
314: }
315:
316: private static ResultSetDataAtom getRandomAtom() {
317: ResultSetDataAtom ret = new ResultSetDataAtom(atom_ids,
318: new CompoundKey(new String[] { String
319: .valueOf(atom_serial_counter++) }));
320: ret.addValue("random", String.valueOf(Math.random()));
321: return ret;
322: }
323:
324: private static UpdateDelta getIncrementalDelta() {
325: UpdateDelta ud = new UpdateDelta("bla-agent", "bla-query",
326: "bla-session");
327: ud.setReplacement(false);
328:
329: ud.getAddedList().add(getRandomAtom());
330: ud.getAddedList().add(getRandomAtom());
331: ud.getAddedList().add(getRandomAtom());
332: ud.getAddedList().add(getRandomAtom());
333: ud.getChangedList().add(getRandomAtom());
334: ud.getRemovedList().add(getRandomAtom());
335: ud.getRemovedList().add(getRandomAtom());
336: ud.getRemovedList().add(getRandomAtom());
337:
338: return ud;
339: }
340:
341: private static UpdateDelta getReplacementDelta() {
342: UpdateDelta ud = new UpdateDelta("bla-agent", "bla-query",
343: "bla-session");
344: ud.setReplacement(true);
345:
346: ud.getReplacementList().add(getRandomAtom());
347: ud.getReplacementList().add(getRandomAtom());
348:
349: return ud;
350: }
351:
352: private static UpdateDelta getErrorDelta() {
353: UpdateDelta ud = new UpdateDelta("bla-agent", "bla-query",
354: "bla-session");
355: ud.setErrorReport(new Exception("bla-error"));
356: return ud;
357: }
358: }
|