001: /*
002: * Enhydra Java Application Server Project
003: *
004: * The contents of this file are subject to the Enhydra Public License
005: * Version 1.1 (the "License"); you may not use this file except in
006: * compliance with the License. You may obtain a copy of the License on
007: * the Enhydra web site ( http://www.enhydra.org/ ).
008: *
009: * Software distributed under the License is distributed on an "AS IS"
010: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
011: * the License for the specific terms governing rights and limitations
012: * under the License.
013: *
014: * The Initial Developer of the Enhydra Application Server is Lutris
015: * Technologies, Inc. The Enhydra Application Server and portions created
016: * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
017: * All Rights Reserved.
018: *
019: * Contributor(s):
020: *
021: * $Id: ConfigFile.java,v 1.2 2006-06-15 14:07:00 sinisa Exp $
022: */
023:
024: package com.lutris.util;
025:
026: import java.io.File;
027: import java.io.IOException;
028: import java.io.InputStream;
029: import java.io.OutputStream;
030: import java.io.PrintWriter;
031: import java.util.HashMap;
032: import java.util.Hashtable;
033: import java.util.Iterator;
034: import java.util.Set;
035: import java.util.Vector;
036:
037: import org.enhydra.util.AbsConfigFile;
038: import org.enhydra.util.ConfConfiguration;
039: import org.enhydra.util.JNDIAdapter;
040:
041: /**
042: * ConfigFile is used to manipulate Multiserver configuration (*.conf) files.
043: *
044: * Presents all configuration elements in the form of a keyed
045: * table. Configuration elements are grouped by string "keys" according
046: * to the function they configure. The syntax is described more formally
047: * below.
048: * <PRE>
049: * stream ::= entry | stream entry
050: * entry ::= key "=" value_list | comment | blank
051: * value_list ::= value | value_list "," value
052: * value ::= fragment | value "+" fragment
053: * fragment ::= key | quoted_string
054: * quoted_string ::= (C/C++ style quoted string)
055: * key ::= (A string matching [A-Za-z_\./][A-Za-z0-9_-\./]*)
056: * comment ::= "#" (any text up to a newline)
057: * blank ::= (A line containing only white space)
058: * </PRE>
059: * In addition to the above syntax, some additional semantic rules apply.
060: * The operator "+" concatenates the fragment immediately to the left
061: * and to the right of it. Thus <code>ab + cd</code> results in
062: * <code>abcd</code>. The operator "," terminates one element and begins
063: * the next element. Thus, the line <code>val = hello, world</code>
064: * creates a configuration entry with the key "val" and the two elements
065: * "hello", and "world". If the characters "+", ",", or "\" occur at
066: * the end of a line, then the entry is continued on the next line.
067: * Trailing and leading whitespaces are ignored, and multiple whitespace
068: * characters are converted by default into a single space. The "+"
069: * operator leaves no whitespace between operands.
070: * Finally, within quoted strings, C-style backslash escapes are
071: * recognized. These include "\n" for newline, "\t" for tab, etc.
072: * An example configuration input file is given below.
073: * <PRE>
074: * #==============================================================
075: * # Sample.config
076: * #==============================================================
077: * LDAP_SERVERS[] = "server1.ldap.gov:338",
078: * "server2.ldap.gov:1000"
079: * USER_TIMEOUT = 3600 # seconds. Comments can follow on same line.
080: * STRANGE_ENTRY = "This is a long long long long long " +
081: * "long long line. Notice how the \"+\" " +
082: * "operator is used to break this line up " +
083: * "into more than one line in the config file." ,
084: * "And this is the second element."
085: * # etc.
086: * </PRE>
087: *
088: * @see Config
089: * @author Shawn McMurdo
090: * @version $Revision: 1.2 $
091: */
092: public class ConfigFile extends AbsConfigFile {
093:
094: /**
095: * Constructor from an InputStream.
096: * @param inputStream The input stream from which to parse the config file.
097: * @exception ConfigException
098: */
099: public ConfigFile(InputStream inputStream) throws ConfigException {
100: config = new Config();
101: order = new Vector();
102: comments = new Hashtable();
103: ConfigParser parser = new ConfigParser(inputStream);
104: try {
105: parser.process(this );
106: } catch (ParseException e) {
107: throw new ConfigException(ConfigException.SYNTAX, e
108: .getMessage());
109: }
110:
111: //SV 25.03.2005.
112: try {
113: readJndi();
114: } catch (Exception e) {
115: }
116:
117: }
118:
119: public ConfigFile() {
120: super ();
121: }
122:
123: /**
124: * Constructor from a File. Allows to later write back the configuration to the
125: * same file.
126: * @param file The local file to parse.
127: * @exception IOException
128: * @exception ConfigException
129: */
130: public ConfigFile(File file) throws ConfigException, IOException {
131: super (file);
132: }
133:
134: /**
135: * Constructor from a KeywordValueTable.
136: * @param kvt A KeywordValueTable from which to populate the config file.
137: * @exception ConfigException
138: */
139: public ConfigFile(KeywordValueTable kvt) throws ConfigException {
140: super (kvt);
141: }
142:
143: /**
144: * Reads application configuration parameters by using JNDI Context.
145: */
146: protected void readJndi() throws ConfigException {
147: try {
148: if (jndiAdapt == null) {
149: try {
150: jndiAdapt = new JNDIAdapter(this .file
151: .getAbsolutePath(),
152: "org.enhydra.spi.conf.ConfFileInitialContextFactory");
153: } catch (Exception e) {
154: jndiAdapt = new JNDIAdapter();
155: }
156: }
157:
158: String[] leafKeys = jndiAdapt.leafKeys();
159: if (leafKeys != null) {
160: int leafKeysLength = leafKeys.length;
161: String newKey = null;
162: String stringValue;
163: String[] stringValues;
164: boolean has_configuration_keys = false;
165: ConfConfiguration configuration_value = null;
166: for (int i = 0; i < leafKeysLength; i++) {
167: String leafKey = leafKeys[i];
168: if (leafKey != null) {
169: if (leafKey
170: .equals("configuration/ConfConfigurationFactory")) {
171: has_configuration_keys = true;
172: configuration_value = (ConfConfiguration) jndiAdapt
173: .get("configuration/ConfConfigurationFactory");
174: } else {
175: if (jndiAdapt.isArray(leafKey)) {
176: newKey = jndiAdapt
177: .removeArrayMark(leafKey);
178: if (newKey != null) {
179: stringValues = jndiAdapt
180: .getStrings(newKey);
181: newKey = jndiAdapt
182: .makeConfigString(newKey);
183: config.set(newKey, stringValues);
184: if (!order.contains(newKey))
185: order.addElement(newKey);
186: comments.put(newKey, "");
187: }
188: } else {
189: Object ovalue = jndiAdapt.get(leafKey);
190: newKey = jndiAdapt
191: .makeConfigString(leafKey);
192:
193: // stringValue = (String)jndiAdapt.get(leafKey);
194: if (ovalue instanceof java.lang.String) {
195: stringValue = (String) ovalue;
196: if (stringValue.startsWith("jndi:")) {
197:
198: stringValue = stringValue
199: .substring(5);
200: Object resource = jndiAdapt
201: .getResource(stringValue);
202: if (resource != null) {
203: config
204: .set(newKey,
205: resource);
206: jndiParameterNames
207: .put(newKey,
208: stringValue);
209: } else {
210: config.set(newKey, "jndi:"
211: + stringValue);
212: }
213: } else {
214: config.set(newKey, stringValue);
215: }
216: if ((newKey != null)
217: && (!order.contains(newKey))) {
218: order.addElement(newKey);
219: }
220: comments.put(newKey, "");
221: } else {
222: }
223: } // else
224: } // else from if (leafKey.equals("configuration/ConfConfigurationFactory"))
225: } // if (leafKey != null)
226: } // for
227: if (has_configuration_keys
228: && (configuration_value != null)) {
229: HashMap elems = configuration_value.getResults();
230: Set set1 = elems.keySet();
231: Iterator iter1 = set1.iterator();
232: while (iter1.hasNext()) {
233: String key = (String) iter1.next();
234: if (jndiAdapt.isArray(key)) {
235: stringValues = (String[]) elems.get(key);
236: newKey = jndiAdapt.removeArrayMark(key);
237: newKey = jndiAdapt.makeConfigString(newKey);
238: config.set(newKey, stringValues);
239: if (!order.contains(newKey))
240: order.addElement(newKey);
241: comments.put(newKey, "");
242: } else {
243: stringValue = (String) elems.get(key);
244:
245: newKey = jndiAdapt.makeConfigString(key);
246:
247: if (stringValue.startsWith("jndi:")) {
248: stringValue = stringValue.substring(5);
249: Object resource = jndiAdapt
250: .getResource(stringValue);
251: if (resource != null) {
252: config.set(newKey, resource);
253: jndiParameterNames.put(newKey,
254: stringValue);
255: } else {
256: config.set(newKey, "jndi:"
257: + stringValue);
258: }
259: } else {
260: config.set(newKey, stringValue);
261: }
262: if (!order.contains(newKey))
263: order.addElement(newKey);
264: comments.put(newKey, "");
265: }
266: } // while
267: } // if (has_configuration_keys)
268:
269: }
270: } // try
271: catch (Exception e) {
272: //System.err.println("Error readJndi conf file");
273: //e.printStackTrace();
274: throw new ConfigException(
275: "Error in reading JNDI configuration parameters.");
276: }
277: }
278:
279: /**
280: * Writes out a config file to the OutputStream specified. Note that Objects
281: * other than String or String[] will be converted into a String.
282: * @param outputStream The output stream on which to write the config file.
283: */
284: public void write(OutputStream outputStream) {
285: PrintWriter out = new PrintWriter(outputStream, true);
286: boolean isArray = false;
287: String key;
288: String comment;
289: String[] values;
290: Hashtable remaining = new Hashtable();
291: String[] remainingkeys = config.leafKeys();
292:
293: // Set up the remaining keys list
294: // The value doesn't matter, just the key
295: for (int i = 0; i < remainingkeys.length; i++)
296: remaining.put(remainingkeys[i], "X");
297:
298: // Do all the entries for which we have comments
299: for (int i = 0; i < order.size(); i++) {
300: key = (String) order.elementAt(i);
301: comment = (String) comments.get(key);
302: isArray = false;
303: try {
304: Object o = config.get(key);
305: if (o == null) {
306: continue;
307: }
308: isArray = o.getClass().isArray();
309: if (isArray) {
310: Object[] oa = (Object[]) o;
311: if ((oa.length > 0)
312: && (oa[0] instanceof java.lang.String))
313: values = (String[]) o;
314: else {
315: values = new String[oa.length];
316: for (int k = 0; k < oa.length; k++)
317: values[k] = oa[k].toString();
318: }
319: } else {
320: values = new String[1];
321: if (o instanceof java.lang.String)
322: values[0] = (String) o;
323: else
324: values[0] = o.toString();
325: }
326: } catch (KeywordValueException e) {
327: values = null;
328: }
329: // write out entry
330: if ((values == null) || (values.length == 0)) {
331: if ((comment != null) && !(comment.equals(""))) {
332: if (comment.endsWith("\n"))
333: out.print(comment);
334: else
335: out.println(comment);
336: }
337: out.print(key);
338: if (isArray)
339: out.print("[]");
340: out.println(" =");
341: } else {
342: if ((comment != null) && !(comment.equals(""))) {
343: if (comment.endsWith("\n"))
344: out.print(comment);
345: else
346: out.println(comment);
347: }
348: if (isArray)
349: out.print(key + "[] = " + quoteStr(values[0]));
350: else {
351: if (jndiParameterNames.containsKey(key)) {
352: String newValueString = "jndi:"
353: + (String) jndiParameterNames.get(key);
354: out.print(key + " = "
355: + quoteStr(newValueString));
356: // jndiParameterNames.remove(key);
357: } else {
358: out.print(key + " = " + quoteStr(values[0]));
359: }
360:
361: }
362: for (int j = 1; j < values.length; j++)
363: out.print(", " + quoteStr(values[j]));
364: out.println();
365: }
366: remaining.remove(key);
367: }
368:
369: // Do the trailing comment
370: comment = (String) comments.get(TRAILING_COMMENT);
371: if ((comment != null) && !(comment.equals(""))) {
372: if (comment.endsWith("\n")) {
373: out.print(comment);
374: } else {
375: out.println(comment);
376: }
377: }
378: remaining.remove(TRAILING_COMMENT);
379:
380: // The new keys are in a hash table with no order
381: // sort them here so that they will be grouped correctly
382: // in the conf file. It makes it easier to read.
383: // XXX This is dependent on JDK 1.2 so comment it out
384: // XXX until we don't support JDK 1.1 anymore.
385: /* XXX
386: java.util.Arrays.sort(remainingkeys,new Comparator() {
387: public int compare(Object o1, Object o2) {
388: String s1 = (String) o1;
389: return s1.compareTo((String) o2);
390: }
391: });
392: XXX */
393:
394: int i = 0;
395: String lastWord = "";
396: while (i != remainingkeys.length) {
397: key = remainingkeys[i];
398: i++;
399: // Do the remaining config entries
400: if (remaining.get(key) != null) {
401: isArray = false;
402: if (!key.startsWith(lastWord))
403: out.println("");
404: int dot = key.indexOf('.');
405: if (dot == -1)
406: dot = key.length();
407: lastWord = key.substring(0, dot);
408:
409: try {
410: Object o = config.get(key);
411: if (o == null)
412: continue;
413: isArray = o.getClass().isArray();
414: if (isArray) {
415: Object[] oa = (Object[]) o;
416: if (oa[0] instanceof java.lang.String)
417: values = (String[]) o;
418: else {
419: values = new String[oa.length];
420: for (int k = 0; k < oa.length; k++)
421: values[k] = oa[k].toString();
422: }
423: } else {
424: values = new String[1];
425: if (o instanceof java.lang.String)
426: values[0] = (String) o;
427: else
428: values[0] = o.toString();
429: }
430: } catch (KeywordValueException e) {
431: values = null;
432: }
433: // write out entry
434: if ((values == null) || (values.length == 0)) {
435: out.println(key + " =");
436: } else {
437: if (isArray)
438: out.print(key + "[] = " + quoteStr(values[0]));
439: else
440: out.print(key + " = " + quoteStr(values[0]));
441:
442: for (int j = 1; j < values.length; j++)
443: out.print(", " + quoteStr(values[j])); // VR 14.12.2002
444: // out.print(key + ", " + quoteStr(values[j])); VR 14.12.2002
445: out.println();
446: }
447: }
448: }
449: }
450:
451: private boolean containsWhiteSpace(String str) {
452: if (str.indexOf(" ") != -1) {
453: return true;
454: } else if (str.indexOf("\t") != -1) {
455: return true;
456: }
457: return false;
458: }
459:
460: private static final String quoteStr(String s) {
461: if ((s == null) || (s.length() < 1))
462: return "";
463: char[] chars = s.toCharArray();
464: StringBuffer sb = new StringBuffer();
465: boolean needQuotes = false;
466: for (int i = 0; i < chars.length; i++) {
467: switch (chars[i]) {
468: // Chars that get special backquotes
469: case '\n':
470: needQuotes = true;
471: sb.append("\\n");
472: break;
473: case '\b':
474: needQuotes = true;
475: sb.append("\\b");
476: break;
477: case '\r':
478: needQuotes = true;
479: sb.append("\\r");
480: break;
481: case '\f':
482: needQuotes = true;
483: sb.append("\\f");
484: break;
485: case '"':
486: needQuotes = true;
487: sb.append("\\\"");
488: break;
489: case '\\':
490: needQuotes = true;
491: sb.append("\\\\");
492: break;
493:
494: // Chars that cause the string to be enclosed in
495: // double quotes.
496: case '\t':
497: case ' ':
498: case '!':
499: case '#':
500: case '$':
501: case '%':
502: case '&':
503: case '\'':
504: case '(':
505: case ')':
506: case '*':
507: case '+':
508: case ',':
509: case '/':
510: case ':':
511: case ';':
512: case '<':
513: case '=':
514: case '>':
515: case '?':
516: case '[':
517: case ']':
518: case '^':
519: case '`':
520: case '{':
521: case '|':
522: case '}':
523: case '~':
524: needQuotes = true;
525: sb.append(chars[i]);
526: break;
527:
528: // All other characters.
529: default:
530: if ((chars[i] < ' ') || (chars[i] == 0x7f)) {
531: needQuotes = true;
532: int ival = (int) chars[i];
533: sb.append('\\');
534: sb.append(digits[(ival & 0xc0) >> 6]);
535: sb.append(digits[(ival & 0x38) >> 3]);
536: sb.append(digits[(ival & 0x07)]);
537: } else if (chars[i] > 0x7f) {
538: needQuotes = true;
539: int ival = (int) chars[i];
540: sb.append("\\u");
541: sb.append(digits[(ival & 0xf000) >> 12]);
542: sb.append(digits[(ival & 0x0f00) >> 8]);
543: sb.append(digits[(ival & 0x00f0) >> 4]);
544: sb.append(digits[(ival & 0x000f)]);
545: } else
546: sb.append(chars[i]);
547: }
548: }
549: if (needQuotes)
550: return ("\"" + sb.toString() + "\"");
551: return sb.toString();
552: }
553:
554: private static final char[] digits = { '0', '1', '2', '3', '4',
555: '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
556:
557: }
|