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: */package org.cougaar.tools.csmart.recipe;
026:
027: import org.cougaar.core.agent.Agent;
028: import org.cougaar.core.agent.SimpleAgent;
029: import org.cougaar.tools.csmart.core.cdata.AgentComponentData;
030: import org.cougaar.tools.csmart.core.cdata.ComponentData;
031: import org.cougaar.tools.csmart.core.cdata.GenericComponentData;
032: import org.cougaar.tools.csmart.core.db.DBUtils;
033: import org.cougaar.tools.csmart.core.db.PopulateDb;
034: import org.cougaar.tools.csmart.core.property.BaseComponent;
035: import org.cougaar.tools.csmart.core.property.ConfigurableComponent;
036: import org.cougaar.tools.csmart.core.property.ConfigurableComponentPropertyAdapter;
037: import org.cougaar.tools.csmart.core.property.ModifiableComponent;
038: import org.cougaar.tools.csmart.core.property.ModificationEvent;
039: import org.cougaar.tools.csmart.core.property.Property;
040: import org.cougaar.tools.csmart.core.property.PropertyEvent;
041: import org.cougaar.tools.csmart.society.AgentComponent;
042: import org.cougaar.tools.csmart.society.ComponentBase;
043: import org.cougaar.tools.csmart.society.cdata.AgentCDataComponent;
044: import org.cougaar.tools.csmart.society.cdata.BaseCDataComponent;
045: import org.cougaar.tools.csmart.society.db.AgentDBComponent;
046: import org.cougaar.tools.csmart.ui.viewer.GUIUtils;
047: import org.cougaar.util.DBProperties;
048:
049: import java.io.IOException;
050: import java.io.Serializable;
051: import java.lang.reflect.Constructor;
052: import java.net.URL;
053: import java.sql.Connection;
054: import java.sql.ResultSet;
055: import java.sql.SQLException;
056: import java.sql.Statement;
057: import java.util.ArrayList;
058: import java.util.Collection;
059: import java.util.HashMap;
060: import java.util.Iterator;
061: import java.util.List;
062: import java.util.Map;
063:
064: /**
065: * ComplexRecipeBase.java
066: *
067: * Base component for a ComplexRecipe. A ComplexRecipe is a recipe that requires
068: * storage in both the recipe tables and an assembly in the asb table.
069: * The primary key is an assembly Id which is a hidden property of the recipe.
070: * This key is stored as the primary argument in the recipe args table.
071: * On load, the assembly Id is used to obtain all recipe related data from the
072: * assembly tables.
073: *
074: * Created: Thu Jun 20 13:52:13 2002
075: *
076: */
077: public class ComplexRecipeBase extends RecipeBase implements
078: ComplexRecipeComponent, Serializable {
079:
080: protected static String DESCRIPTION_RESOURCE_NAME = "";
081:
082: /** Identifier String for the hidden Assembly Id Property **/
083: public static final String ASSEMBLY_PROP = "Assembly Id";
084:
085: private static final String QUERY_AGENT_NAMES = "queryAgentNames";
086: private static final String QUERY_PLUGIN_NAME = "queryPluginNames";
087: private static final String QUERY_PLUGIN_ARGS = "queryComponentArgs";
088:
089: public static final String PROP_TARGET_COMPONENT_QUERY = "Target Component Selection Query";
090: public static final String PROP_TARGET_COMPONENT_QUERY_DFLT = "recipeQuerySelectNothing";
091: public static final String PROP_TARGET_COMPONENT_QUERY_DESC = "The query name for selecting components to modify";
092:
093: protected String recipeId = null;
094: protected String assemblyId = null;
095: protected String oldAssemblyId = null;
096: protected ComponentData cdata = null;
097: private Property propAssemblyId;
098: private String initName = null;
099: private Map substitutions = new HashMap();
100: private transient DBProperties dbp;
101:
102: public ComplexRecipeBase(String name) {
103: super (name);
104: this .initName = name;
105: }
106:
107: public ComplexRecipeBase(String name, String assemblyId) {
108: super (name);
109: this .assemblyId = assemblyId;
110: this .initName = name;
111: }
112:
113: public ComplexRecipeBase(String name, String assemblyId,
114: String recipeId) {
115: super (name);
116: this .assemblyId = assemblyId;
117: this .initName = name;
118: this .recipeId = recipeId;
119: }
120:
121: public ComplexRecipeBase(String name, String assemblyId,
122: String recipeId, String initName) {
123: super (name);
124: this .assemblyId = assemblyId;
125: this .initName = initName;
126: this .recipeId = recipeId;
127: }
128:
129: public ComplexRecipeBase(ComponentData cdata, String assemblyId) {
130: super (cdata.getName());
131: this .cdata = cdata;
132: this .assemblyId = assemblyId;
133: }
134:
135: /**
136: * Returns the description of this society
137: *
138: * @return an <code>URL</code> value
139: */
140: public URL getDescription() {
141: return getClass().getResource(DESCRIPTION_RESOURCE_NAME);
142:
143: }
144:
145: /**
146: * Save the recipe to the database. This save performs the
147: * saving of all data to the assembly tables. It then calls
148: * it's parents save to save the assembly Id and recipe name
149: * to the recipe tables.
150: *
151: * @return a <code>boolean</code> value
152: */
153: public boolean saveToDatabase() {
154: // First, save the new recipe assembly.
155: saveInProgress = true;
156: if (log.isInfoEnabled()) {
157: log.info("saveToDatabase recipe (" + getRecipeName()
158: + ") with asb: " + getAssemblyId()
159: + " and old Assembly: " + oldAssemblyId);
160: }
161:
162: // TODO:
163: // Should I notice when I need to save and only save then?
164: // Should I resist creating a new assembly ID, to avoid
165: // breaking other experiments? Or always create one?
166:
167: String oldCMTAsbid = oldAssemblyId;
168: String currAssID = getAssemblyId();
169: if (currAssID == null || currAssID.trim().equals(""))
170: currAssID = null;
171: String name = getRecipeName();
172:
173: // And what is my current assemblyID?
174: // How do I know if it is different?
175: // FIXME: Maybe I need a new
176:
177: // But probably only want to pass it in if it was in fact a CMT assembly, no?
178: // Or does it hurt to pass it in?
179: PopulateDb pdb = null;
180: boolean ret = true;
181: try {
182: // FIXME: Is there a non-gui conflict handler I should use?
183: pdb = new PopulateDb(oldCMTAsbid, name, currAssID, GUIUtils
184: .createSaveToDbConflictHandler(null), true);
185: pdb.populateCSA(getComponentData());
186: // Set the new CSA assembly ID on the society - get it from the PDB
187: // setAssemblyId(pdb.getCMTAssemblyId());
188: assemblyId = pdb.getCMTAssemblyId();
189: propAssemblyId.setValue(assemblyId);
190: // What about fixAssemblies?
191: // is it really populateCSA?
192: pdb.close();
193: } catch (Exception sqle) {
194: if (log.isErrorEnabled()) {
195: log.error("Error saving recipe to database: ", sqle);
196: }
197: ret = false;
198: } finally {
199: if (pdb != null) {
200: try {
201: pdb.close();
202: } catch (SQLException e) {
203: }
204: }
205: }
206:
207: propAssemblyId.setVisible(true);
208: ret &= super .saveToDatabase();
209: propAssemblyId.setVisible(false);
210:
211: return ret;
212: }
213:
214: public void addTargetQueryProperty() {
215: Property p = addRecipeQueryProperty(
216: PROP_TARGET_COMPONENT_QUERY,
217: PROP_TARGET_COMPONENT_QUERY_DFLT);
218: p.setToolTip(PROP_TARGET_COMPONENT_QUERY_DESC);
219: }
220:
221: protected Property addRecipeQueryProperty(String name, String dflt) {
222: Property prop = addProperty(new RecipeQueryProperty(this , name,
223: dflt));
224: prop.setPropertyClass(String.class);
225: return prop;
226: }
227:
228: /**
229: * Initializes the hidden assembly id property, or CDATA
230: *
231: */
232: public void initProperties() {
233: if (cdata != null) {
234: initFromCData();
235: assemblyId = null;
236: } else if (assemblyId != null) {
237: initFromDatabase();
238: }
239:
240: propAssemblyId = addProperty(ASSEMBLY_PROP,
241: ((assemblyId != null) ? assemblyId : ""));
242: propAssemblyId.setVisible(false);
243:
244: // Bug 2021: Must listen to changes to the Agent name
245: // property and reject name same as the recipe name. Note we could
246: // have done this in AgentDBComponent, but that would be uglier.
247: // loop over children and look for the property
248: for (int i = 0; i < getChildCount(); i++) {
249: Property propName = ((ConfigurableComponent) getChild(i))
250: .getProperty(AgentDBComponent.PROP_AGENT_NAME);
251: if (propName != null) {
252: propName
253: .addPropertyListener(new ConfigurableComponentPropertyAdapter() {
254: public void propertyValueChanged(
255: PropertyEvent e) {
256: String newVal = (String) e
257: .getProperty().getValue();
258: if (getRecipeName().equals(newVal)) {
259: if (log.isDebugEnabled())
260: log
261: .debug("Complex recipe: agent name changing to recipe name ("
262: + newVal
263: + ")! Will reject.");
264: String old = (String) e
265: .getPreviousValue();
266: e.getProperty().setValue(old);
267: }
268: }
269: });
270: }
271: }
272:
273: if (!getRecipeName().equals(initName)) {
274: // A recipe copy - mark it as modified
275: oldAssemblyId = assemblyId;
276: assemblyId = "";
277: propAssemblyId.setValue("");
278: modified = true;
279: } else {
280: // After reading from the DB, it is not modified.
281: modified = false;
282: fireModification(new ModificationEvent(this , RECIPE_SAVED));
283: }
284: }
285:
286: // Initialize recipe details from the DB, from an RCP assembly
287: private void initFromDatabase() {
288: if (assemblyId != null) {
289: try {
290: dbp = DBProperties.readQueryFile(DBUtils.QUERY_FILE,
291: "csmart");
292: } catch (IOException ioe) {
293: if (log.isErrorEnabled()) {
294: log.error("IO Exception reading Query File", ioe);
295: }
296: }
297:
298: initAgentsFromDb(); // Get the Agents
299: initCompsFromDb(); // Get the other components
300: initTargetsFromDb(); // Get the target query over-rides for the other comps
301: }
302: }
303:
304: // Get the Agent components to be added from the DB
305: private void initAgentsFromDb() {
306: Map substitutions = new HashMap();
307: if (assemblyId != null) {
308: substitutions.put(":assemblyMatch", DBUtils
309: .getListMatch(assemblyId));
310: substitutions
311: .put(":insertion_point", Agent.INSERTION_POINT);
312:
313: try {
314: Connection conn = DBUtils.getConnection();
315: try {
316: Statement stmt = conn.createStatement();
317: String query = DBUtils.getQuery(QUERY_AGENT_NAMES,
318: substitutions);
319: ResultSet rs = stmt.executeQuery(query);
320: while (rs.next()) {
321: String agentName = DBUtils.getNonNullString(rs,
322: 1, query);
323: if (log.isDebugEnabled())
324: log.debug("initAgsFromDb adding "
325: + agentName);
326: AgentDBComponent agent = new AgentDBComponent(
327: agentName, assemblyId);
328: agent.initProperties();
329: addChild(agent);
330: }
331: rs.close();
332: stmt.close();
333: } finally {
334: conn.close();
335: }
336: } catch (Exception e) {
337: if (log.isErrorEnabled()) {
338: log.error("Exception", e);
339: }
340: throw new RuntimeException("Error" + e);
341: }
342: }
343: }
344:
345: // Initialize non-Agent sub-components from DB
346: private void initCompsFromDb() {
347: String assemblyMatch = DBUtils.getListMatch(assemblyId);
348: if (assemblyMatch != null) {
349: substitutions.put(":assemblyMatch", assemblyMatch);
350: // Warning: Since we use the recipe name here,
351: // we'll only find the components in the DB if the DB lists them
352: // with parent of the this recipe name
353: substitutions.put(":agent_name", initName);
354: }
355:
356: List types = new ArrayList();
357: types.add(ComponentData.SOCIETY);
358: types.add(ComponentData.NODE);
359: types.add(ComponentData.HOST);
360: types.add(ComponentData.NODEAGENT);
361: types.add(ComponentData.AGENT);
362: types.add(ComponentData.RECIPE);
363: String ctypematch = "NOT " + DBUtils.getListMatch(types);
364: substitutions.put(":comp_type:", ctypematch);
365:
366: // Get Plugin Names, class names, and parameters
367: try {
368: Connection conn = DBUtils.getConnection();
369: String query = "";
370: try {
371: Statement stmt = conn.createStatement();
372: query = dbp.getQuery(QUERY_PLUGIN_NAME, substitutions);
373:
374: if (log.isDebugEnabled())
375: log.debug("initCompsFromDb doing query " + query);
376:
377: ResultSet rs = stmt.executeQuery(query);
378: while (rs.next()) {
379: String pluginClassName = rs.getString(1);
380:
381: if (log.isDebugEnabled())
382: log.debug("initFromDb loading "
383: + pluginClassName);
384:
385: String pluginName = rs.getString(4);
386: String pluginType = rs.getString(5).trim().intern();
387: String priority = rs.getString(6).intern();
388: ComponentBase plugin = new ComponentBase(
389: pluginName, pluginClassName, priority);
390: plugin.initProperties();
391: String alibId = rs.getString(2);
392: String libId = rs.getString(3);
393: plugin.setAlibID(alibId);
394: plugin.setLibID(libId);
395: plugin.setComponentType(pluginType);
396: // set the type too
397: substitutions.put(":comp_alib_id", alibId);
398: substitutions.put(":comp_id", rs.getString(3));
399: Statement stmt2 = conn.createStatement();
400: String query2 = dbp.getQuery(QUERY_PLUGIN_ARGS,
401: substitutions);
402: ResultSet rs2 = stmt2.executeQuery(query2);
403: while (rs2.next()) {
404: String arg = rs2.getString(1);
405: plugin.addParameter(arg);
406: }
407: rs2.close();
408: stmt2.close();
409: addChild(plugin);
410: } // end of loop over plugins to add
411: rs.close();
412: stmt.close();
413: } finally {
414: conn.close();
415: }
416: } catch (Exception e) {
417: if (log.isErrorEnabled()) {
418: log.error("initCompsFromDb Exception: ", e);
419: }
420: }
421: }
422:
423: // Initialize target query over-rides from DB
424: private void initTargetsFromDb() {
425: if (log.isDebugEnabled()) {
426: log.debug("In initTargetsFromDb");
427: }
428:
429: Map substitutions = new HashMap();
430: if (recipeId != null) {
431: substitutions.put(":recipe_id", recipeId);
432:
433: try {
434: Connection conn = DBUtils.getConnection();
435: try {
436: Statement stmt = conn.createStatement();
437: String query = DBUtils.getQuery(
438: "queryRecipeProperties", substitutions);
439: if (log.isDebugEnabled()) {
440: log.debug("initTargetsFromDb: Run query: "
441: + query);
442: }
443:
444: ResultSet rs = stmt.executeQuery(query);
445: while (rs.next()) {
446: String propName = rs.getString(1);
447: if (propName.startsWith("$$CP=")) {
448: int start = propName.indexOf("=");
449: String component = propName
450: .substring(start + 1);
451:
452: // Find the Component and add the Property
453: if (log.isDebugEnabled()) {
454: log.debug("Looking for: " + component
455: + " out of " + getChildCount()
456: + " children");
457: }
458: boolean foundit = false;
459: for (int i = 0; i < getChildCount(); i++) {
460: ConfigurableComponent cc = (ConfigurableComponent) getChild(i);
461: // ComponentBase cc = (ComponentBase)getChild(i);
462: if (log.isDebugEnabled())
463: log.debug("Considering child " + i
464: + ": " + cc.getShortName());
465:
466: if (cc instanceof ComponentBase) {
467: String tag = ((ComponentBase) cc)
468: .getComponentClassName()
469: + "-" + i;
470: if (log.isDebugEnabled()) {
471: log.debug("Compare with: "
472: + tag);
473: }
474: if (tag.equals(component)) {
475: if (log.isDebugEnabled()) {
476: log
477: .debug("Found Desired Parent: "
478: + tag
479: + " adding parameter");
480: }
481: cc
482: .addProperty(
483: PROP_TARGET_COMPONENT_QUERY,
484: rs.getString(2));
485: foundit = true;
486: break;
487: }
488: } else {
489: // What kind of component is this?
490: if (cc instanceof AgentComponent) {
491: log
492: .debug("not right - it's an Agent");
493: }
494: }
495: } // loop over children
496: if (!foundit) {
497: if (log.isWarnEnabled())
498: log
499: .warn("Unable to find component "
500: + component
501: + " to set override target query on: "
502: + propName);
503: }
504: } else if (!propName.equals(ASSEMBLY_PROP)) {
505: // Non child component property
506: Property prop = getProperty(propName);
507: if (prop != null) {
508: if (log.isDebugEnabled())
509: log.debug("setting local prop "
510: + propName + " to "
511: + rs.getString(2));
512: prop.setValue(rs.getString(2));
513: } else {
514: if (log.isDebugEnabled())
515: log.debug("Adding new local prop "
516: + propName + " with val "
517: + rs.getString(2));
518: addProperty(propName, rs.getString(2));
519: }
520: }
521: } // end of loop over props from db
522: rs.close();
523: stmt.close();
524: } finally {
525: conn.close();
526: }
527: } catch (Exception e) {
528: if (log.isErrorEnabled()) {
529: log.error(
530: "Exception getting target properties for recipe "
531: + getRecipeName(), e);
532: }
533: }
534: }
535: }
536:
537: private void initFromCData() {
538: // create society properties from cdata
539: // create agents from cdata
540: ArrayList agentData = new ArrayList();
541: ArrayList componentData = new ArrayList();
542: ArrayList alldata = new ArrayList();
543: if (cdata != null)
544: alldata.add(cdata);
545:
546: // Find all the data.
547: for (int i = 0; i < alldata.size(); i++) {
548: ComponentData someData = (ComponentData) alldata.get(i);
549: String type = someData.getType();
550: if (type.equals(ComponentData.AGENT)) {
551: agentData.add(someData);
552: } else if (type.equals(ComponentData.SOCIETY)
553: || type.equals(ComponentData.HOST)
554: || type.equals(ComponentData.NODE)
555: || type.equals(ComponentData.RECIPE)) {
556: // It's not something I know - some sort of container
557: ComponentData[] moreData = someData.getChildren();
558: for (int j = 0; j < moreData.length; j++)
559: alldata.add(moreData[j]);
560: } else {
561: // Add it directly
562: componentData.add(someData);
563: }
564: }
565:
566: // For each agent, create a component and add it as a child
567: for (int i = 0; i < agentData.size(); i++) {
568: AgentComponent agentComponent = new AgentCDataComponent(
569: (ComponentData) agentData.get(i));
570: agentComponent.initProperties();
571: addChild(agentComponent);
572: }
573:
574: // Add my immediate children
575: for (int j = 0; j < componentData.size(); j++) {
576: BaseComponent baseComponent = new BaseCDataComponent(
577: (ComponentData) componentData.get(j));
578: baseComponent.initProperties();
579: addChild(baseComponent);
580: }
581: }
582:
583: /**
584: * Get the assembly id for this Recipe.
585: * @return a <code>String</code> which is the assembly id for this Recipe
586: */
587: public String getAssemblyId() {
588: return this .assemblyId;
589: }
590:
591: protected ComponentData getComponentData() {
592: ComponentData cd = new GenericComponentData();
593: cd.setType(ComponentData.RECIPE);
594: cd.setClassName(RECIPE_CLASS);
595: cd.setName(getRecipeName());
596: cd.setOwner(null);
597: cd.setParent(null);
598:
599: AgentComponent[] agents = getAgents();
600: for (int i = 0; i < agents.length; i++) {
601: generateAgentComponentData(agents[i], cd, null);
602: }
603:
604: // Now let all components add their data.
605: addComponentData(cd);
606:
607: return modifyComponentData(cd);
608:
609: // FIXME: Why are we calling addComponentData twice?
610: // return addComponentData(cd);
611: }
612:
613: /**
614: * Adds all the component data relevant to this recipe.
615: *
616: * @param data
617: * @return a <code>ComponentData</code> value
618: */
619: public ComponentData addComponentData(ComponentData data) {
620: ComponentData[] children = data.getChildren();
621: if (children == null)
622: return data;
623: for (int i = 0; i < children.length; i++) {
624: ComponentData child = children[i];
625: // for each child component data, if it's an agent's component data
626: if (child.getType() == ComponentData.AGENT) {
627: // get all my agent components
628: Iterator iter = ((Collection) getDescendentsOfClass(AgentComponent.class))
629: .iterator();
630: while (iter.hasNext()) {
631: AgentComponent agent = (AgentComponent) iter.next();
632: // if the component data name matches the agent name
633: if (child.getName().equals(
634: agent.getShortName().toString())) {
635: // then set me as the owner of the component data
636: child.setOwner(this );
637: // and add the component data
638: agent.addComponentData(child);
639: }
640: }
641: } else {
642: // FIXME!! Will we support other top-level components?
643: // Process children of component data
644: addComponentData(child);
645: }
646: }
647: return data;
648: }
649:
650: private static final void generateAgentComponentData(
651: AgentComponent agent, ComponentData parent,
652: ConfigurableComponent owner) {
653:
654: AgentComponentData ac = new AgentComponentData();
655: ac.setName(agent.getShortName());
656: ac.setClassName(SimpleAgent.class.getName());
657: ac.addParameter(agent.getShortName().toString()); // Agents have one parameter, the agent name
658: ac.setOwner(owner);
659: ac.setParent(parent);
660: parent.addChild((ComponentData) ac);
661: }
662:
663: private static Class[] constructorParams = { ComponentData.class,
664: String.class };
665:
666: /**
667: * Copies a ComplexRecipe
668: *
669: * @param name
670: * @return a <code>ModifiableComponent</code> value
671: */
672: public ModifiableComponent copy(String name) {
673:
674: ComponentData cdata = getComponentData();
675: cdata.setName(name);
676: RecipeComponent component = null;
677: // Use reflection so we create a recipe of the correct type.
678: try {
679: Constructor cons = this .getClass().getConstructor(
680: constructorParams);
681: component = (RecipeComponent) cons
682: .newInstance(new Object[] { cdata, null });
683: } catch (Exception e) {
684: if (log.isErrorEnabled())
685: log.error("copy: Failed to create a copy of class "
686: + this .getClass(), e);
687: return null;
688: }
689: component.initProperties();
690:
691: // FIXME: Next line means the copy initialized from CDATA will not be
692: // marked modified if this one was not, when in fact it is not
693: // entirely in the DB, right?
694: // ((ComplexRecipeBase)component).modified = this.modified;
695: ((ComplexRecipeBase) component).oldAssemblyId = getAssemblyId();
696:
697: return component;
698: }
699:
700: }// ComplexRecipeBase
|