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: /*
028: * Originally from delta/fgi package mil.darpa.log.alpine.delta.plugin;
029: * Copyright 1997 BBN Systems and Technologies, A Division of BBN Corporation
030: * 10 Moulton Street, Cambridge, MA 02138 (617) 873-3000
031: */
032: package org.cougaar.util;
033:
034: import java.io.BufferedInputStream;
035: import java.io.FileInputStream;
036: import java.io.IOException;
037: import java.io.InputStream;
038: import java.net.URL;
039: import java.util.Enumeration;
040: import java.util.HashMap;
041: import java.util.Iterator;
042: import java.util.Map;
043:
044: import org.cougaar.util.log.Logger;
045: import org.cougaar.util.log.Logging;
046:
047: /**
048: * This utility extends <code>java.util.Properties</code> by doing parameter
049: * substitutions after loading a .q file. q files are cached, such that they
050: * are only parsed once per VM.
051: *
052: * .q files understood by this class are different from those
053: * understood by QueryLDMPlugin and LDMSQLPlugin because the files are
054: * not divided into query handler sections. Instead query handlers are
055: * specified (when necessary) by the following convention: a property
056: * named after the generic query type (e.g. locationQuery) and
057: * suffixed with .handler is defined with a comma-separated list of
058: * handler class names. Each handler class knows the name of the query
059: * property that it uses.
060: **/
061: public abstract class DBProperties extends java.util.Properties {
062: protected static final Logger log = Logging
063: .getLogger(DBProperties.class);
064:
065: private String default_dbtype;
066: private boolean debug = false;
067: private String name;
068:
069: // Stash away DBProperties by .q file name as we create them
070: private static Map dbProps = new HashMap();
071:
072: /**
073: * Get the cached DBProperties instance for this file, if any
074: *
075: * @param qFile a <code>String</code> query file to look up
076: * @return a <code>DBProperties</code> for that file, possibly null
077: */
078: private static DBProperties getCachedInstance(String qFile) {
079: synchronized (dbProps) {
080: if (qFile == null)
081: return null;
082: return ((DBProperties) dbProps.get(qFile));
083: }
084: }
085:
086: /**
087: * Stash away DBProperties as they are created by q file name
088: *
089: * @param instance a <code>DBProperties</code> instance to reuse
090: * @param qFile a <code>String</code> query file name
091: */
092: private static void addCachedInstance(DBProperties instance,
093: String qFile) {
094: synchronized (dbProps) {
095: if (qFile != null && instance != null
096: && !dbProps.containsKey(qFile))
097: dbProps.put(qFile, instance);
098: }
099: }
100:
101: /**
102: * Stash away DBProperties as they are created by q file name, regardless
103: * of whether the named .q file was previously stashed away.
104: *
105: * @param instance a <code>DBProperties</code> instance to reuse
106: * @param qFile a <code>String</code> query file name
107: */
108: private static void setCachedInstance(DBProperties instance,
109: String qFile) {
110: synchronized (dbProps) {
111: if (qFile != null && instance != null)
112: dbProps.put(qFile, instance);
113: }
114: }
115:
116: /**
117: * Read and parse a .q file. If the file was previously parsed, do not
118: * re-read. If the .q file contains value for
119: * "database" (case-sensitive) the default database type is set accordingly. The
120: * default database type may be set or changed manually with the
121: * setDefaultDatabase method.
122: * @param qfile the name of the query file
123: **/
124: public static DBProperties readQueryFile(String qfile)
125: throws IOException {
126: return DBProperties.readQueryFile(qfile, null);
127: }
128:
129: /**
130: * Read and parse a .q file. If the file was previously parsed, do not
131: * re-read. If the .q file contains value for
132: * "database" (case-sensitive) the default database type is set accordingly. The
133: * default database type may be set or changed manually with the
134: * setDefaultDatabase method.
135: * @param qfile the name of the query file
136: * @param module the name of the module to search first for the query file
137: **/
138: public static DBProperties readQueryFile(String qfile, String module)
139: throws IOException {
140: // Only create a DBProperties if we dont have one yet for this file
141: DBProperties dbp = DBProperties.getCachedInstance(qfile);
142: if (dbp == null) {
143: dbp = createDBProperties(qfile, ConfigFinder.getInstance(
144: module).open(qfile));
145: if (dbp != null)
146: DBProperties.addCachedInstance(dbp, qfile);
147: }
148: return dbp;
149: }
150:
151: /**
152: * Force Re-Read and parse a .q file. If the .q file contains value for
153: * "database" (case-sensitive) the default database type is set accordingly. The
154: * default database type may be set or changed manually with the
155: * setDefaultDatabase method.
156: * @param qfile the name of the query file
157: **/
158: public static DBProperties reReadQueryFile(String qfile)
159: throws IOException {
160: return DBProperties.reReadQueryFile(qfile, null);
161: }
162:
163: /**
164: * Force Re-Read and parse a .q file. If the .q file contains value for
165: * "database" (case-sensitive) the default database type is set accordingly. The
166: * default database type may be set or changed manually with the
167: * setDefaultDatabase method.
168: * @param qfile the name of the query file
169: * @param module the name of the module to search first for the query file
170: **/
171: public static DBProperties reReadQueryFile(String qfile,
172: String module) throws IOException {
173: // Force re-reading the q file, and cache it.
174: DBProperties dbp = createDBProperties(qfile, ConfigFinder
175: .getInstance(module).open(qfile));
176: if (dbp != null)
177: DBProperties.setCachedInstance(dbp, qfile);
178: return dbp;
179: }
180:
181: /**
182: * Read and parse a .q file specified as a URL. If the file was
183: * previously read, do not re-read. If the .q file
184: * contains a value for "database" (case-sensitive) the default database type is
185: * set accordingly. The default database type may be set or
186: * changed manually with the setDefaultDatabase method.
187: * @param url the url to be opened to read the query file
188: * contents.
189: **/
190: public static DBProperties readQueryFile(URL url)
191: throws IOException {
192: // Only create a DBProperties if we dont have one yet for this file
193: String qfile = url.toString();
194: DBProperties dbp = DBProperties.getCachedInstance(qfile);
195: if (dbp == null) {
196: dbp = createDBProperties(qfile, url.openStream());
197: if (dbp != null)
198: DBProperties.addCachedInstance(dbp, qfile);
199: }
200: return dbp;
201: }
202:
203: /**
204: * Force Re-read and parse a .q file specified as a URL. Even if the file was
205: * previously read, re-read. If the .q file
206: * contains a value for "database" (case-sensitive) the default database type is
207: * set accordingly. The default database type may be set or
208: * changed manually with the setDefaultDatabase method.
209: * @param url the url to be opened to read the query file contents.
210: **/
211: public static DBProperties reReadQueryFile(URL url)
212: throws IOException {
213: // Only create a DBProperties if we dont have one yet for this file
214: String qfile = url.toString();
215: DBProperties dbp = createDBProperties(qfile, url.openStream());
216: if (dbp != null)
217: DBProperties.setCachedInstance(dbp, qfile);
218: return dbp;
219: }
220:
221: private static DBProperties createDBProperties(String name,
222: InputStream is) throws IOException {
223: InputStream i = new BufferedInputStream(is);
224: try {
225: return new Immutable(name, i);
226: } catch (RuntimeException re) {
227: log.error("CreateDBProperties exception", re);
228: throw re;
229: } catch (IOException ioe) {
230: log.error("CreateDBProperties exception", ioe);
231: throw ioe;
232: } finally {
233: i.close();
234: }
235: }
236:
237: //
238: // constructors
239: //
240:
241: protected DBProperties(DBProperties that) {
242: for (Enumeration props = that.propertyNames(); props
243: .hasMoreElements();) {
244: String key = (String) props.nextElement();
245: super .setProperty(key, that.getProperty(key));
246: }
247: default_dbtype = that.getDefaultDatabase();
248: name = that.getName();
249: }
250:
251: protected DBProperties(String name, InputStream i)
252: throws IOException {
253: this .name = name;
254: load(i);
255: String dbts = getProperty("database");
256: if (dbts != null) {
257: default_dbtype = getDBType(dbts);
258: }
259: }
260:
261: //
262: // accessors
263: //
264:
265: /**
266: * Add the queries from the given query file to this instance's collection.
267: *
268: * @param qfile a <code>String</code> query file to parse
269: * @exception IOException if an error occurs
270: */
271: public void addQueryFile(String qfile) throws IOException {
272: addQueryFile(qfile, null);
273: }
274:
275: /**
276: * Add the queries from the given query file to this instance's collection.
277: *
278: * @param qfile a <code>String</code> query file to parse
279: * @param module the name of the module to search first for the query file
280: * @exception IOException if an error occurs
281: */
282: public void addQueryFile(String qfile, String module)
283: throws IOException {
284: InputStream i = new BufferedInputStream(ConfigFinder
285: .getInstance(module).open(qfile));
286: try {
287: load(i);
288: } catch (RuntimeException re) {
289: log.error("addQueryFile exception", re);
290: throw re;
291: } catch (IOException ioe) {
292: log.error("addQueryFile exception", ioe);
293: throw ioe;
294: } finally {
295: i.close();
296: }
297: }
298:
299: public String getName() {
300: return name;
301: }
302:
303: /**
304: * Change the database specification. The database specification
305: * is the name of a database url parameter found using in this
306: * DBProperties.
307: * @param dburl the jdbc url of the database in which the queries
308: * are to be executed. The database type is extracted from the url
309: * and used for getting queries tailored for that database.
310: **/
311: public void setDefaultDatabase(String dburl) {
312: default_dbtype = getDBType(dburl);
313: }
314:
315: public String getDefaultDatabase() {
316: return default_dbtype;
317: }
318:
319: /**
320: * Accessor for the default database type string. This is the
321: * string that is appended to query names (e.g. oracle) to form
322: * the name of a database-specific query (or other value).
323: **/
324: public String getDBType() {
325: return default_dbtype;
326: }
327:
328: /**
329: * Convert a db url into a database type string
330: **/
331: public String getDBType(String dburl) {
332: if (dburl == null) {
333: log.error("Missing DB URL!");
334: throw new IllegalArgumentException("Missing DB URL!");
335: }
336: int ix1 = dburl.indexOf("jdbc:") + 5;
337: int ix2 = dburl.indexOf(":", ix1);
338: if (ix1 < 0 || ix2 < 0) {
339: log.error("Malformed DB Url: " + dburl);
340: throw new IllegalArgumentException("Malformed DB URL: "
341: + dburl);
342: }
343: return dburl.substring(ix1, ix2);
344: }
345:
346: /**
347: * Load properties from an InputStream and post-process to perform
348: * variable substitutions on the values. Substitution is done
349: * using Parameters.replaceParameters.
350: * This method should not normally be used and is defined only to
351: * override the base class version and interpose parameter
352: * replacements from cougaar.rc.
353: **/
354: public void load(InputStream i) throws IOException {
355: super .load(i);
356: for (Enumeration e = propertyNames(); e.hasMoreElements();) {
357: String name = (String) e.nextElement();
358: String rawValue = getProperty(name);
359: String cookedValue = Parameters.replaceParameters(rawValue,
360: this );
361: setProperty(name, cookedValue);
362: }
363: }
364:
365: /**
366: * Return a query with a given name. Variable substitution is
367: * performed by looking for patterns which are the keys in a Map.
368: * By convention, variable names start with a colon and are
369: * alphanumeric, but this is not required. However, variable names
370: * must not start with the name of another variable. For example,
371: * :a and :aye are not allowed. The query is sought under two
372: * different names, first by suffixing the given name with a dot
373: * (.) and the default database type and, if that query is not
374: * found, again without the suffix.
375: * @param queryName the name of the query.
376: * @param substitutions The substitutions to be performed. The
377: * query is examined for occurances of the keys in the Map and
378: * replaced with the corresponding value.
379: **/
380: public String getQuery(String queryName, Map substitutions) {
381: return getQueryForDatabase(queryName, substitutions, null);
382: }
383:
384: /**
385: * Same as {@link #getQuery(String,Map) above}, but allows a
386: * different database to be specified.
387: * @param queryName the name of the query.
388: * @param substitutions a map of translations of substitution variables
389: * @param dbspec the key under which to find the database url for
390: * this query.
391: **/
392: public String getQueryForDatabase(String queryName,
393: Map substitutions, String dbspec) {
394: String dbtype = default_dbtype;
395: ;
396: if (dbspec != null) {
397: String dburl = getProperty(dbspec);
398: if (dburl != null) {
399: dbtype = getDBType(dburl);
400: }
401: }
402: String result = getQuery1(queryName + "." + dbtype,
403: substitutions);
404: if (result == null)
405: result = getQuery1(queryName, substitutions);
406: if (result == null)
407: throw new IllegalArgumentException("No query named "
408: + queryName);
409: return result;
410: }
411:
412: /**
413: * Does the actual work. Called twice for each query.
414: **/
415: private String getQuery1(String queryName, Map substitutions) {
416: String tmp = getProperty(queryName);
417: if (tmp == null)
418: return null;
419:
420: StringBuffer query = new StringBuffer(tmp);
421: if (substitutions != null) {
422: for (Iterator entries = substitutions.entrySet().iterator(); entries
423: .hasNext();) {
424: Map.Entry entry = (Map.Entry) entries.next();
425: String key = (String) entry.getKey();
426: int ix = 0;
427: while ((ix = query.indexOf(key, ix)) >= 0) {
428: String subst = (String) entry.getValue();
429: if (subst == null)
430: throw new IllegalArgumentException(
431: "Null value for " + key);
432: else {
433: query.replace(ix, ix + key.length(), subst);
434: ix = ix + key.length();
435: }
436: }
437: }
438: }
439: if (debug) {
440: System.out.println(this + ": " + queryName + "->" + query);
441: }
442: return query.substring(0);
443: }
444:
445: /**
446: * Enable debugging. When debugging is enabled, the queries are
447: * printed after substitution has been performed
448: **/
449: public void setDebug(boolean newDebug) {
450: debug = newDebug;
451: }
452:
453: public String toString() {
454: return name;
455: }
456:
457: /** Return a locked copy of this DBProperties object.
458: **/
459: public abstract DBProperties lock();
460:
461: /** Return an unlocked copy of this DBProperties object.
462: **/
463: public abstract DBProperties unlock();
464:
465: public abstract boolean isLocked();
466:
467: //
468: // instantiable classes
469: //
470:
471: /** A mutable DBProperties class **/
472: private static class Mutable extends DBProperties {
473: public Mutable(DBProperties p) {
474: super (p);
475: }
476:
477: public Object clone() {
478: return new Mutable(this );
479: }
480:
481: public DBProperties lock() {
482: return new Immutable(this );
483: }
484:
485: public DBProperties unlock() {
486: return new Mutable(this );
487: }
488:
489: public boolean isLocked() {
490: return false;
491: }
492: }
493:
494: /** An immutable variation of DBProperties **/
495: static class Immutable extends DBProperties {
496: public Immutable(DBProperties p) {
497: super (p);
498: lockdown = true;
499: }
500:
501: public Immutable(String name, InputStream i) throws IOException {
502: super (name, i);
503: lockdown = true;
504: }
505:
506: private boolean lockdown = false;
507:
508: public void load(InputStream isStream) throws IOException {
509: if (lockdown) {
510: //log.error("Attempt to modify Immutable instance", new Throwable());
511: throw new IllegalArgumentException(
512: "Immutable DBProperties instance");
513: }
514: super .load(isStream);
515: }
516:
517: public void clear() {
518: //log.error("Attempt to modify Immutable instance", new Throwable());
519: throw new IllegalArgumentException(
520: "Immutable DBProperties instance");
521: }
522:
523: public void putAll(Map m) {
524: //log.error("Attempt to modify Immutable instance", new Throwable());
525: throw new IllegalArgumentException(
526: "Immutable DBProperties instance");
527: }
528:
529: public Object remove(Object key) {
530: //log.error("Attempt to modify Immutable instance", new Throwable());
531: throw new IllegalArgumentException(
532: "Immutable DBProperties instance");
533: }
534:
535: public Object put(Object key, Object value) {
536: if (lockdown) {
537: //log.error("Attempt to modify Immutable instance", new Throwable());
538: throw new IllegalArgumentException(
539: "Immutable DBProperties instance");
540: }
541: return super .put(key, value);
542: }
543:
544: public Object clone() {
545: return this ;
546: }
547:
548: public void setDefaultDatabase(String dburl) {
549: //log.error("Attempt to modify Immutable instance", new Throwable());
550: throw new IllegalArgumentException(
551: "Immutable DBProperties instance");
552: }
553:
554: public DBProperties lock() {
555: return this ;
556: }
557:
558: public DBProperties unlock() {
559: return new Mutable(this );
560: }
561:
562: public boolean isLocked() {
563: return true;
564: }
565:
566: public void addQueryFile(String qfile, String module) {
567: //log.error("Attempt to modify Immutable instance", new Throwable());
568: throw new IllegalArgumentException(
569: "Immutable DBProperties instance");
570: }
571: }
572: }
|