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.query;
028:
029: import java.io.Serializable;
030: import java.lang.reflect.Method;
031: import java.util.HashMap;
032: import java.util.Iterator;
033: import java.util.LinkedList;
034: import java.util.List;
035: import java.util.Map;
036: import java.util.StringTokenizer;
037:
038: import org.cougaar.lib.aggagent.script.PythAggregator;
039: import org.cougaar.lib.aggagent.script.PythAlert;
040: import org.cougaar.lib.aggagent.script.PythIncrementFormat;
041: import org.cougaar.lib.aggagent.script.PythMelder;
042: import org.cougaar.lib.aggagent.script.PythUnaryPredicate;
043: import org.cougaar.lib.aggagent.script.PythXMLEncoder;
044: import org.cougaar.lib.aggagent.script.SilkAggregator;
045: import org.cougaar.lib.aggagent.script.SilkAlert;
046: import org.cougaar.lib.aggagent.script.SilkIncrementFormat;
047: import org.cougaar.lib.aggagent.script.SilkMelder;
048: import org.cougaar.lib.aggagent.script.SilkUnaryPredicate;
049: import org.cougaar.lib.aggagent.script.SilkXMLEncoder;
050: import org.cougaar.lib.aggagent.session.IncrementFormat;
051: import org.cougaar.lib.aggagent.session.XMLEncoder;
052: import org.cougaar.lib.aggagent.session.XmlIncrement;
053: import org.cougaar.lib.aggagent.util.InverseSax;
054: import org.cougaar.lib.aggagent.util.XmlUtils;
055: import org.cougaar.lib.aggagent.util.Enum.AggType;
056: import org.cougaar.lib.aggagent.util.Enum.Language;
057: import org.cougaar.lib.aggagent.util.Enum.ScriptType;
058: import org.cougaar.lib.aggagent.util.Enum.XmlFormat;
059: import org.cougaar.util.UnaryPredicate;
060: import org.w3c.dom.Element;
061: import org.w3c.dom.NodeList;
062:
063: /**
064: * An instance of this class may be used to represent a script, including the
065: * code itself plus a variety of information concerning the intended usage of
066: * the script.
067: */
068: public class ScriptSpec implements Serializable {
069: private static String CLASS_TAG = "class";
070: private static String PARAM_TAG = "param";
071: private static String LANGUAGE_ATT = "language";
072: private static String TYPE_ATT = "type";
073: private static String AGG_IDS_ATT = "aggIds";
074: private static String NAME_ATT = "name";
075:
076: private static Class[] STRING_PARAM = new Class[] { String.class };
077:
078: private ScriptType type = null;
079: private Language lang = null;
080:
081: // only used when the script is for XML encoding
082: private XmlFormat format = null;
083:
084: // only used when the script is intended for data set aggregation
085: private AggType aggType = null;
086: private List aggIds = null;
087:
088: private String text = null;
089: private Map params = new HashMap();
090:
091: /**
092: * Tell the language in which this script is written. See Enum.Language
093: * for details.
094: */
095: public Language getLanguage() {
096: return lang;
097: }
098:
099: /**
100: * Tell the basic purpose of the script. See Enum.ScriptType for details.
101: */
102: public ScriptType getType() {
103: return type;
104: }
105:
106: /**
107: * Retrieve the type of XML encoding that this script provides. See
108: * Enum.XmlFormat for details. This value is only meaningful if the script
109: * type is Enum.ScriptType.INCREMENT_FORMAT, and should be ignored otherwise.
110: */
111: public XmlFormat getFormat() {
112: return format;
113: }
114:
115: /**
116: * Retrieve the type of aggregation that this script provides. See
117: * Enum.AggType for details. This value is only meaningful if the script
118: * type is Enum.ScriptType.AGGREGATOR, and should be ignored otherwise.
119: */
120: public AggType getAggType() {
121: return aggType;
122: }
123:
124: /**
125: * Supply the caller with the text of the script. In the case of a Java
126: * representation, the class name is returned.
127: */
128: public String getText() {
129: return text;
130: }
131:
132: /**
133: * Create a new ScriptSpec for the provided script with the purpose and
134: * language specified. Everything else is left as its default value.
135: */
136: public ScriptSpec(ScriptType t, Language l, String s) {
137: type = t;
138: lang = l;
139: text = s;
140: }
141:
142: /**
143: * Create a new ScriptSpec for an IncrementFormat, with the language and
144: * XmlFormat specified.
145: */
146: public ScriptSpec(Language l, XmlFormat f, String s) {
147: this (ScriptType.INCREMENT_FORMAT, l, s);
148: format = f;
149: }
150:
151: /**
152: * Create a new ScriptSpec for an Aggregator, with the aggregation type,
153: * collation IDs, and script language specified.
154: */
155: public ScriptSpec(Language l, AggType agg, String s, String ids) {
156: this (ScriptType.AGGREGATOR, l, s);
157: aggType = agg;
158: aggIds = parseAggIds(ids);
159: }
160:
161: /**
162: * Create a new ScriptSpec for a Java implementation. Here, the purpose
163: * of the script, the class name, and a parameter map for the Java class are
164: * supplied by the caller.
165: */
166: public ScriptSpec(ScriptType t, String s, Map p) {
167: this (t, Language.JAVA, s);
168: if (p != null)
169: params.putAll(p);
170: }
171:
172: /**
173: * Create a new ScriptSpec for an IncrementFormat in Java. The XmlFormat,
174: * class name, and parameter list are provided by the caller.
175: */
176: public ScriptSpec(XmlFormat f, String s, Map p) {
177: this (Language.JAVA, f, s);
178: if (p != null)
179: params.putAll(p);
180: }
181:
182: /**
183: * Create a new ScriptSpec for an Aggregator in Java. The caller supplies
184: * the AggType, class name, collation IDs, and a parameter map for the Java
185: * class.
186: */
187: public ScriptSpec(AggType agg, String s, String ids, Map p) {
188: type = ScriptType.AGGREGATOR;
189: lang = Language.JAVA;
190: aggType = agg;
191: text = s;
192: aggIds = parseAggIds(ids);
193: if (p != null)
194: params.putAll(p);
195: }
196:
197: /**
198: * Reconstitute a ScriptSpec that has been converted to XML. In effect,
199: * this constructor is the inverse of the toXml() method of this class.
200: */
201: public ScriptSpec(Element root) {
202: type = ScriptType.fromString(root.getNodeName());
203: lang = Language.fromString(root.getAttribute(LANGUAGE_ATT));
204: if (type == ScriptType.INCREMENT_FORMAT)
205: format = XmlFormat.fromString(root.getAttribute(TYPE_ATT));
206: if (type == ScriptType.AGGREGATOR) {
207: aggType = AggType.fromString(root.getAttribute(TYPE_ATT));
208: aggIds = parseAggIds(root.getAttribute(AGG_IDS_ATT));
209: }
210:
211: if (lang == Language.JAVA)
212: parseJavaSpec(root);
213: else
214: parseScriptSpec(root);
215: }
216:
217: private static List parseAggIds(String s) {
218: if (s == null)
219: return null;
220:
221: List ret = new LinkedList();
222: StringTokenizer tok = new StringTokenizer(s, " ,;\t\r\n");
223: while (tok.hasMoreTokens())
224: ret.add(tok.nextToken());
225:
226: return ret;
227: }
228:
229: private static String encodeAggIds(List l) {
230: if (l == null)
231: return null;
232:
233: StringBuffer buf = new StringBuffer();
234: Iterator i = l.iterator();
235: if (i.hasNext()) {
236: buf.append(i.next());
237: while (i.hasNext()) {
238: buf.append(" ");
239: buf.append(i.next());
240: }
241: }
242: return buf.toString();
243: }
244:
245: private void parseJavaSpec(Element elt) {
246: NodeList nl = elt.getElementsByTagName(CLASS_TAG);
247: if (nl.getLength() > 0)
248: text = XmlUtils.getElementText((Element) nl.item(0)).trim();
249:
250: nl = elt.getElementsByTagName(PARAM_TAG);
251: for (int i = 0; i < nl.getLength(); i++) {
252: Element p = (Element) nl.item(i);
253: String name = p.getAttribute(NAME_ATT);
254: String val = XmlUtils.getElementText(p);
255: params.put(name, val);
256: }
257: }
258:
259: private void parseScriptSpec(Element elt) {
260: text = XmlUtils.getElementText(elt);
261: }
262:
263: /**
264: * Convert this ScriptSpec to XML for transmission over a network. The
265: * parsed XML document can then be used to reconstruct the ScriptSpec using
266: * the constructor that takes an Element as its argument.
267: */
268: public String toXml() {
269: InverseSax doc = new InverseSax();
270: includeXml(doc);
271: return doc.toString();
272: }
273:
274: public void includeXml(InverseSax doc) {
275: doc.addElement(type.toString());
276: doc.addAttribute(LANGUAGE_ATT, lang.toString());
277: if (format != null)
278: doc.addAttribute(TYPE_ATT, format.toString());
279: if (aggType != null)
280: doc.addAttribute(TYPE_ATT, aggType.toString());
281: if (aggIds != null)
282: doc.addAttribute(AGG_IDS_ATT, encodeAggIds(aggIds));
283:
284: if (lang == Language.JAVA)
285: includeJavaXml(doc);
286: else
287: doc.addText(text);
288:
289: doc.endElement();
290: }
291:
292: private void includeJavaXml(InverseSax doc) {
293: doc.addTextElement(CLASS_TAG, text);
294: for (Iterator i = params.entrySet().iterator(); i.hasNext();) {
295: Map.Entry p = (Map.Entry) i.next();
296: doc.addEltAttText(PARAM_TAG, NAME_ATT, p.getKey()
297: .toString(), p.getValue().toString());
298: }
299: }
300:
301: /**
302: * Retrieve the list of collation IDs for an Aggregator script.
303: */
304: public String getAggIdString() {
305: if (aggIds != null)
306: return encodeAggIds(aggIds);
307: return null;
308: }
309:
310: private static String propertySetterName(String property) {
311: if (property == null || property.length() == 0)
312: throw new IllegalArgumentException();
313:
314: StringBuffer buf = new StringBuffer("set");
315: buf.append(Character.toUpperCase(property.charAt(0)));
316: buf.append(property.substring(1));
317: return buf.toString();
318: }
319:
320: private void setBeanProperties(Object o) throws Exception {
321: Class c = o.getClass();
322: for (Iterator i = params.entrySet().iterator(); i.hasNext();) {
323: Map.Entry p = (Map.Entry) i.next();
324: String name = propertySetterName((String) p.getKey());
325: Method m = c.getMethod(name, STRING_PARAM);
326: m.invoke(o, new Object[] { p.getValue() });
327: }
328: }
329:
330: private Object makeBean() throws Exception {
331: Object bean = null;
332: if (text != null) {
333: bean = Class.forName(text).newInstance();
334: setBeanProperties(bean);
335: }
336: return bean;
337: }
338:
339: private void checkTypeMatch(ScriptType makeType) throws Exception {
340: if (makeType != type)
341: throw new Exception("Cannot make " + makeType + " from a "
342: + type + " script");
343: }
344:
345: /**
346: * Create a UnaryPredicate from this ScriptSpec, if appropriate. If the
347: * language spec is not one of those recognized, then this method will
348: * return null. If the script is not a UnaryPredicate script, then an
349: * Exception will be raised.
350: */
351: public UnaryPredicate toUnaryPredicate() throws Exception {
352: checkTypeMatch(ScriptType.UNARY_PREDICATE);
353: if (lang == Language.JPYTHON)
354: return PythUnaryPredicate.predicateFromScript(text);
355: else if (lang == Language.SILK)
356: return new SilkUnaryPredicate(text);
357: else if (lang == Language.JAVA)
358: return (UnaryPredicate) makeBean();
359: return null;
360: }
361:
362: /**
363: * Create an Alert from this ScriptSpec, if appropriate. If the language
364: * spec is not one of those recognized, then this method will return null.
365: * If the script is not an Alert script, then an Exception will be raised.
366: */
367: public Alert toAlert() throws Exception {
368: checkTypeMatch(ScriptType.ALERT);
369: if (lang == Language.JPYTHON)
370: return PythAlert.parseAlert(text);
371: else if (lang == Language.SILK)
372: return new SilkAlert(text);
373: else if (lang == Language.JAVA)
374: return (Alert) makeBean();
375: return null;
376: }
377:
378: private IncrementFormat toScriptedFormat() throws Exception {
379: if (lang == Language.JPYTHON)
380: return PythIncrementFormat.formatFromScript(text);
381: else if (lang == Language.SILK)
382: return new SilkIncrementFormat(text);
383: else if (lang == Language.JAVA)
384: return (IncrementFormat) makeBean();
385: return null;
386: }
387:
388: private XMLEncoder toXMLEncoder() throws Exception {
389: if (lang == Language.JPYTHON)
390: return PythXMLEncoder.encoderFromScript(text);
391: else if (lang == Language.SILK)
392: return new SilkXMLEncoder(text);
393: else if (lang == Language.JAVA)
394: return (XMLEncoder) makeBean();
395: return null;
396: }
397:
398: /**
399: * Create an IncrementFormat from this ScriptSpec, if appropriate. If the
400: * language spec is not one of those recognized, then this method will
401: * return null. If the script is not an IncrementFormat script, then an
402: * Exception will be raised.
403: */
404: public IncrementFormat toIncrementFormat() throws Exception {
405: checkTypeMatch(ScriptType.INCREMENT_FORMAT);
406: if (format == XmlFormat.INCREMENT)
407: return toScriptedFormat();
408: else if (format == XmlFormat.XMLENCODER)
409: return new XmlIncrement(toXMLEncoder());
410: return null;
411: }
412:
413: private Aggregator toScriptedAggregator() throws Exception {
414: if (lang == Language.JPYTHON)
415: return PythAggregator.aggregatorFromScript(text);
416: else if (lang == Language.SILK)
417: return new SilkAggregator(text);
418: else if (lang == Language.JAVA)
419: return (Aggregator) makeBean();
420: return null;
421: }
422:
423: private DataAtomMelder toDataAtomMelder() throws Exception {
424: if (lang == Language.JPYTHON)
425: return PythMelder.melderFromScript(text);
426: else if (lang == Language.SILK)
427: return new SilkMelder(text);
428: else if (lang == Language.JAVA)
429: return (DataAtomMelder) makeBean();
430: return null;
431: }
432:
433: /**
434: * Create an Aggregator from this ScriptSpec, if appropriate. If the
435: * language spec is not one of those recognized, then this method will
436: * return null. If the script is not an Aggregator script, then an
437: * Exception will be raised.
438: */
439: public Aggregator toAggregator() throws Exception {
440: checkTypeMatch(ScriptType.AGGREGATOR);
441: if (aggType == AggType.AGGREGATOR)
442: return toScriptedAggregator();
443: else if (aggType == AggType.MELDER)
444: return new BatchAggregator(aggIds, toDataAtomMelder());
445: return null;
446: }
447:
448: /**
449: * Convert this ScriptSpec to a Java Object of the appropriate type.
450: */
451: public Object toObject() throws Exception {
452: if (type == ScriptType.UNARY_PREDICATE)
453: return toUnaryPredicate();
454: else if (type == ScriptType.INCREMENT_FORMAT)
455: return toIncrementFormat();
456: else if (type == ScriptType.ALERT)
457: return toAlert();
458: else if (type == ScriptType.AGGREGATOR)
459: return toAggregator();
460: return null;
461: }
462:
463: /**
464: * Create a UnaryPredicate directly from an XML representation. If the
465: * document does not specify a UnaryPredicate, an Exception may be raised.
466: */
467: public static UnaryPredicate makeUnaryPredicate(Element elt)
468: throws Exception {
469: return new ScriptSpec(elt).toUnaryPredicate();
470: }
471:
472: /**
473: * Create an IncrementFormat directly from an XML representation. If the
474: * document does not specify an IncrementFormat, an Exception may be raised.
475: */
476: public static IncrementFormat makeIncrementFormat(Element elt)
477: throws Exception {
478: return new ScriptSpec(elt).toIncrementFormat();
479: }
480:
481: /**
482: * Create an Alert directly from an XML representation. If the document
483: * does not specify an Alert, an Exception may be raised.
484: */
485: public static Alert makeAlert(Element elt) throws Exception {
486: return new ScriptSpec(elt).toAlert();
487: }
488:
489: /**
490: * Create an Aggregator directly from an XML representation. If the
491: * document does not specify an Aggregator, an Exception may be raised.
492: */
493: public static Aggregator makeAggregator(Element elt)
494: throws Exception {
495: return new ScriptSpec(elt).toAggregator();
496: }
497:
498: /**
499: * Create an Object of the appropriate type directly from an XML
500: * representation.
501: */
502: public static Object makeObject(Element elt) throws Exception {
503: return new ScriptSpec(elt).toObject();
504: }
505: }
|