001: /*
002: * <copyright>
003: *
004: * Copyright 1997-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.mlm.plugin.ldm;
028:
029: import java.io.BufferedReader;
030: import java.io.InputStreamReader;
031: import java.sql.Connection;
032: import java.sql.ResultSet;
033: import java.sql.ResultSetMetaData;
034: import java.sql.SQLException;
035: import java.sql.Statement;
036: import java.util.Enumeration;
037: import java.util.Properties;
038: import java.util.Vector;
039:
040: import org.cougaar.core.blackboard.SubscriberException;
041: import org.cougaar.core.component.ServiceRevokedEvent;
042: import org.cougaar.core.component.ServiceRevokedListener;
043: import org.cougaar.core.service.DomainService;
044: import org.cougaar.core.service.LoggingService;
045: import org.cougaar.planning.ldm.LDMServesPlugin;
046: import org.cougaar.planning.ldm.PlanningFactory;
047: import org.cougaar.planning.ldm.asset.Asset;
048: import org.cougaar.util.DBConnectionPool;
049: import org.cougaar.util.Parameters;
050:
051: /**
052: * Provide a JDBC binding to an COUGAAR cluster using MB5.0 interfaces.
053: * This class replaces the JDBCPlugin. Current use involves runtime
054: * creation of LDMObjects at Cluster startup. Future use will involve
055: * dynamic, runtime creation of LDMObjects indirectly, via LDMFactory
056: * methods.
057: *
058: * On one side, we present a normal plugin API. On the other side,
059: * we parse a "Query file" and talk to JDBC and a set of QueryHandler
060: * objects.
061: *
062: * The plugin expects at least one Plugin parameter
063: * (via Plugin.getParameters). The first parameter is
064: * interpreted as the name of a "query file". We look for this file
065: * first in the current directory and then in $alphome/demo/queries/.
066: * Any other parameters are interpreted as query parameter settings,
067: * as though they were parsed from a global section of the query file.
068: * plugin=org.cougaar.mlm.plugin.sql.LDMSQLPlugin(foo.q, NSN=12345669)
069: *
070: * The query file is a description of one or more queries to execute
071: * on behalf of the plugin. Comment lines (lines starting with '#') and
072: * empty lines are ignored. Lines starting with '%' indicate the start
073: * of a "query section" and the rest of the line names the QueryHandler
074: * class to use. All other lines are of the form "parameter=value" where
075: * the parameter values are made available to the current query. Initial
076: * parameter settings (before a '%' line) are considered global and are
077: * inherited by all following queries. In addition, a special case
078: * pseudo-query of "%Global" allows additional entries into the global table.
079: *
080: * A QueryHandler (for now) names a java class to instantiate for the
081: * constructing an SQL expression and then parsing the rows of the results.
082: * If the QueryHandler name does not include a '.' character, the parser
083: * will prepend the value of the "Package" parameter (which defaults to
084: * "org.cougaar.mlm.plugin.sql") in order to resolve the class.
085: *
086: * See QueryHandler for more information.
087: *
088: * Reserved QueryFile Parameters:
089: * Package Default package for QueryHandler classnames
090: * QueryFile the QueryFile of the current Query
091: * Driver JDBC driver to use (oracle is already loaded)
092: * Database JDBC database descriptor (used in getConnection)
093: * Username JDBC Username (also "user")
094: * Password JDBC Password (also "password")
095: *
096: * Also support parameter substitutions in the sql query string.
097: * If the QueryHandler returns
098: * "select nsn, quantity from nsns where nsn = ':NSN'"
099: * the JDBC plugin will substitute the value of parameter NSN
100: * for the string :NSN. A colon character may be escaped with
101: * a backslash.
102: */
103:
104: public class LDMSQLPlugin extends LDMEssentialPlugin //implements SQLService
105: {
106:
107: private static final String DEFAULT_DB_DRIVER = "oracle.jdbc.driver.OracleDriver";
108:
109: DomainService domainService = null;
110: protected PlanningFactory theFactory = null;
111:
112: protected LoggingService log = LoggingService.NULL;
113:
114: public LDMSQLPlugin() {
115: }
116:
117: // the global parameters table (String -> object/String
118: protected Properties globalParameters = new Properties();
119: // list of query objects
120: protected Vector queries = new Vector();
121: // the name of the file to look for.
122: protected String queryFile;
123: //WAN the type of database we are connecting to (Oracle, MySQL...)
124: protected String dbType; //WAN
125:
126: protected void setupSubscriptions() {
127: // let the super run to deal with the uninteresting stuff
128: //super.setupSubscriptions();
129:
130: // get the domain service
131: if (theFactory == null) {
132: domainService = (DomainService) getBindingSite()
133: .getServiceBroker().getService(this ,
134: DomainService.class,
135: new ServiceRevokedListener() {
136: public void serviceRevoked(
137: ServiceRevokedEvent re) {
138: theFactory = null;
139: }
140: });
141: }
142: theFactory = ((PlanningFactory) domainService
143: .getFactory("planning"));
144:
145: LoggingService thelog = (LoggingService) getBindingSite()
146: .getServiceBroker().getService(this ,
147: LoggingService.class,
148: new ServiceRevokedListener() {
149: public void serviceRevoked(
150: ServiceRevokedEvent re) {
151: log = LoggingService.NULL;
152: }
153: });
154: if (thelog != null)
155: log = thelog;
156:
157: // set up the subscription
158: // This could be a future site for maintaining a Container of created
159: // LDMObjects for future updating.
160: //if (!didRehydrate()) { // Objects should already exist after rehydration
161: if (!getBlackboardService().didRehydrate()) {
162: try {
163: // set up initial properties
164: initProperties();
165: // deal with the arguments.
166: grokArguments();
167: // parse the query file into our query vectors and parameters
168: parseQueryFile();
169: // sort the queryhandlers into categories.
170: grokQueries();
171: } catch (SubscriberException se) {
172: log.error("Initialization Failed in "
173: + getMessageAddress(), se);
174: }
175: }
176: }
177:
178: // empty execute
179: public void execute() {
180: }
181:
182: protected void initProperties() {
183: // default package for QueryHandler
184: globalParameters.put("Package", "org.cougaar.mlm.plugin.ldm");
185: globalParameters.put("agent", "'" + getMessageAddress() + "'");
186: //globalParameters.put("PublishOnSelfOrg","true");
187: // if (log.isDebugEnabled())
188: // log.debug("In " + getMessageAddress() + " globalParameters are: "+globalParameters);
189: }
190:
191: // retrieve and parse the arguments
192: protected void grokArguments() {
193: // first, initialize the global table with some basics
194: Vector pv = getParameters();
195: if (pv == null) {
196: throw new RuntimeException(
197: "LDMPlugin requires at least one parameter");
198: } else {
199: boolean isFirst = true;
200: for (Enumeration ps = pv.elements(); ps.hasMoreElements();) {
201: String p = (String) ps.nextElement();
202: if (isFirst) {
203: isFirst = false;
204: globalParameters.put("QueryFile", p);
205: queryFile = p;
206: } else {
207: parseQueryParameter(globalParameters, p);
208: }
209: }
210: if (isFirst) {
211: // no args
212: throw new RuntimeException(
213: "LDMPlugin requires at least one parameter");
214: }
215: }
216: }
217:
218: // parse the query file
219: protected void parseQueryFile() {
220: // if (log.isDebugEnabled())
221: // log.debug("In " + getMessageAddress() + " query file is: "+queryFile);
222: try {
223: BufferedReader in = new BufferedReader(
224: new InputStreamReader(getConfigFinder().open(
225: queryFile)));
226: //BufferedReader in = new BufferedReader(new InputStreamReader(getCluster().getConfigFinder().open(queryFile)));
227: Properties pt = null;
228: for (String line = in.readLine(); line != null; line = in
229: .readLine()) {
230:
231: line = line.trim(); // ugly
232:
233: // skip empty lines
234: if (line.length() == 0)
235: continue;
236:
237: int len;
238: // handle continuation lines
239: while ((len = line.length()) > 0
240: && '\\' == line.charAt(len - 1)) {
241: line = (line.substring(0, len - 1))
242: + (in.readLine().trim());
243: }
244:
245: char c = line.charAt(0);
246:
247: // skip comments
248: if (c == '#')
249: continue;
250:
251: // queryhandler section
252: if (c == '%') {
253: String s = line.substring(1).trim(); // skip the %
254: if (s == null || s.length() == 0
255: || s.equals("Global")) {
256: // global handler parameters
257: pt = null;
258: } else {
259: // s names a queryhandler class (for now)
260: try {
261: if (s.indexOf('.') < 0) { // if the class has no package..
262: // try the default package (which is the same as ours)
263: String pkg = globalParameters
264: .getProperty("Package");
265: if (pkg != null) {
266: s = pkg + "." + s;
267: }
268: }
269: QueryHandler cqh = (QueryHandler) (Class
270: .forName(s).newInstance());
271:
272: pt = (Properties) globalParameters.clone();
273: // if (log.isDebugEnabled())
274: // log.debug("LDMSQLPlugin, pt is: "+pt);
275: cqh.initialize(
276: this , // LDMEssentialPlugin
277: // this, // ldmservice
278: getMessageAddress(), getCluster(),
279: ((PlanningFactory) domainService
280: .getFactory("planning")),
281: pt, getBlackboardService());
282: queries.addElement(cqh);
283: } catch (Exception bogon) {
284: log.error("In " + getMessageAddress()
285: + ", Exception creating " + s,
286: bogon);
287: }
288: }
289: } else if (line.indexOf("select ") != -1) { //WAN if the word "select " is found process as if query
290: int equalsIndex = line.indexOf('='); //find the =
291: String queryType = line.substring(0, equalsIndex);
292: int dotIndex = queryType.indexOf(".");
293: if (dotIndex != -1)
294: queryType = queryType.substring(dotIndex + 1)
295: .trim();
296: else
297: queryType = "default";
298:
299: //only process query if it is a default query or it is a query for the type of database that is being used
300: if (queryType.equals("default")) {
301: parseQueryParameter(
302: ((pt == null) ? globalParameters : pt),
303: line);
304: } else if (queryType.equalsIgnoreCase(dbType)) {
305: StringBuffer newLine = new StringBuffer(line
306: .substring(0, dotIndex));
307: newLine.append(line.substring(equalsIndex));
308: parseQueryParameter(
309: ((pt == null) ? globalParameters : pt),
310: newLine.toString(), true);
311: } else
312: //skip if this query does not match this database type and is not a default query (WAN)
313: continue;
314: } else {
315: // should be a param=value line
316: parseQueryParameter(
317: ((pt == null) ? globalParameters : pt),
318: line);
319: }
320: }
321: in.close();
322:
323: } catch (Exception e) {
324: log.error("In " + getMessageAddress() + ", Error reading '"
325: + queryFile + "'", e);
326: throw new RuntimeException("No QueryFile: " + e);
327: }
328: }
329:
330: /**
331: * This method exposes the protected LDM in the plugin Adapter
332: * to the QueryHandler. Had to name it differently than
333: * getLDM() because the plugin adapter version of getLDM() is final.
334: */
335: public LDMServesPlugin getLDMPlugin() {
336: return getLDM();
337: }
338:
339: private String getDBType(String databaseVal) {
340: int colonIndex1 = databaseVal.indexOf(':');
341: int colonIndex2 = databaseVal.indexOf(':', colonIndex1 + 1);
342: return databaseVal.substring(colonIndex1 + 1, colonIndex2);
343: }
344:
345: private void parseQueryParameter(Properties table, String s) {
346: int i = s.indexOf('=');
347: // if (log.isDebugEnabled())
348: // log.debug( "\ns = " + s );
349: String p = s.substring(0, i).trim();
350: String v = s.substring(i + 1).trim();
351:
352: if (p.equalsIgnoreCase("database")) {//WAN added
353: String realVal = Parameters.replaceParameters(v); //WAN added
354: dbType = getDBType(realVal); //WAN added
355: }
356: if (!table.containsKey(p))
357: table.put(p, v);
358: }
359:
360: // force the database specific query in
361: private void parseQueryParameter(Properties table, String s,
362: boolean dbSpecific) {
363: int i = s.indexOf('=');
364: String p = s.substring(0, i).trim();
365: String v = s.substring(i + 1).trim();
366: if (dbSpecific) // if database specific, overwrite the default
367: table.put(p, v);
368: }
369:
370: /** sort queryhandlers into categories.
371: * PeriodicQueries will get executed synchronously for the first
372: * time here.
373: **/
374: protected void grokQueries() {
375: for (Enumeration e = queries.elements(); e.hasMoreElements();) {
376: QueryHandler qh = (QueryHandler) e.nextElement();
377: qh.start();
378: if (qh instanceof org.cougaar.mlm.plugin.ldm.PrototypeProvider) {
379: prototypeProviders.addElement(qh);
380: } else if (qh instanceof org.cougaar.mlm.plugin.ldm.PropertyProvider) {
381: propertyProviders.addElement(qh);
382: }
383: // else it was just a PeriodicQuery.
384: }
385: }
386:
387: private String produceQuery(QueryHandler qh, String s) {
388: if (s.indexOf(':') < 0) { // no params to handle
389: return s;
390: }
391:
392: char[] scs = s.toCharArray();
393: int l = s.length();
394: StringBuffer sb = new StringBuffer();
395:
396: int i = 0;
397: while (i < l) {
398: char c = scs[i];
399:
400: if (c == '\\') { // quoted?
401: i++;
402: sb.append(scs[i]);
403: i++;
404: } else if (c == ':') { // param subst?
405: i++;
406: // find the end of the var
407: int j;
408: for (j = i; j < l && Character.isLetterOrDigit(scs[j]); j++)
409: ;
410: // j is now the index of the first non symbol char past the colon
411: String param = s.substring(i, j);
412: String value = qh.getParameter(param);
413: if (value == null) {
414: log.error("Subsituting params in agent "
415: + getMessageAddress() + " in '" + s
416: + "' at " + (i - 1)
417: + " got null value for param: " + param);
418: throw new RuntimeException(
419: "Parameter Substitution problem in '" + s
420: + "' at " + (i - 1));
421: }
422: sb.append(value);
423: i = j;
424: } else {
425: sb.append(c);
426: i++;
427: }
428: }
429:
430: // sb contains the substituted string
431: return sb.toString();
432: }
433:
434: //
435: // SQLService interface
436: //
437:
438: public void executeSQL(String rawSql, QueryHandler qh) {
439: try {
440: String dbname = qh.getParameter("Database");
441: if (dbname == null) {
442: throw new RuntimeException("No Connection parameter.");
443: }
444: //WAN find the dbtype by using the value of the dbname delete later
445: //String dbtype = getDBType(dbname); delete later
446: if (dbType == null) {
447: throw new RuntimeException(
448: "No Connection parameter. - No dbtype");
449: }
450:
451: String driver = Parameters
452: .findParameter("driver." + dbType);//WAN must go directly to cougaar.rc now
453: if (driver != null) {
454: DBConnectionPool.registerDriver(driver);
455: } else {
456: if (log.isWarnEnabled())
457: log.warn("In agent " + getMessageAddress()
458: + ": No Driver parameter specified for "
459: + dbType + " - using default driver - "
460: + DEFAULT_DB_DRIVER);
461: DBConnectionPool.registerDriver(DEFAULT_DB_DRIVER);
462: }
463:
464: // do Param substitution
465: String sql;
466: sql = produceQuery(qh, rawSql);
467:
468: Properties props = new Properties();
469: String user = qh.getParameter("Username");
470: if (user == null)
471: user = qh.getParameter("user");
472: String pass = qh.getParameter("Password");
473: if (pass == null)
474: pass = qh.getParameter("password");
475: if (user == null || pass == null)
476: throw new RuntimeException(
477: "Incomplete user/password parameters");
478: props.put("user", user);
479: props.put("password", pass);
480:
481: Connection conn = DBConnectionPool.getConnection(dbname,
482: user, pass);
483: try {
484: Statement statement = conn.createStatement();
485: ResultSet rset = statement.executeQuery(sql);
486: ResultSetMetaData md = rset.getMetaData();
487: int ncols = md.getColumnCount();
488:
489: Object row[] = new Object[ncols];
490:
491: while (rset.next()) {
492: for (int i = 0; i < ncols; i++)
493: row[i] = rset.getObject(i + 1);
494: qh.processRow(row);
495: }
496:
497: statement.close();
498: } catch (SQLException sqe) {
499: log.error("In agent " + getMessageAddress()
500: + ": executeQuery failed for " + sql, sqe);
501: } finally {
502: conn.close();
503: }
504:
505: } catch (Exception e) {
506: log.error("In agent " + getMessageAddress()
507: + ": Caught exception while executing a query", e);
508: }
509: }
510:
511: private Vector prototypeProviders = new Vector();
512: private Vector propertyProviders = new Vector();
513:
514: //
515: // LDMService
516: //
517:
518: public Asset getPrototype(String typeid, Class hint) {
519: for (Enumeration e = prototypeProviders.elements(); e
520: .hasMoreElements();) {
521: PrototypeProvider pp = (PrototypeProvider) e.nextElement();
522: if (pp.canHandle(typeid)) {
523: Asset a = pp.getAssetPrototype(typeid);
524: return a;
525: }
526: }
527: return null;
528: }
529:
530: public Asset getPrototype(String typeid) {
531: return getPrototype(typeid, null);
532: }
533:
534: public void fillProperties(Asset anAsset) {
535: for (Enumeration e = propertyProviders.elements(); e
536: .hasMoreElements();) {
537: PropertyProvider pp = (PropertyProvider) e.nextElement();
538: pp.provideProperties(anAsset);
539: }
540: }
541:
542: }
|