001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.util.xml;
011:
012: import java.util.*;
013: import java.net.URL;
014: import java.io.IOException;
015: import org.mmbase.util.*;
016: import org.mmbase.util.logging.*;
017: import org.w3c.dom.Element;
018:
019: /**
020: * This class reads configuration files for utilities, that are
021: * placed in /config/utils/.
022: *
023: * A typical way to use it may be like so:
024: <pre>
025: private UtilReader.PropertiesMap utilProperties = new UtilReader("myutil.xml", new Runnable() { public void run() { init();}}).getProperties();
026: private void init() {
027: // use utilProperties
028: }
029: {
030: init();
031: }
032: </pre>
033: * This produces a 'watched map' utilProperties. Every time the underlying config file(s) are changed 'init' is called. Init is called on instantation of the surrounding class too.
034: *
035: * @since MMBase-1.6.4
036: * @author Rob Vermeulen
037: * @author Michiel Meeuwissen
038: * @version $Id: UtilReader.java,v 1.33 2007/11/19 15:01:36 michiel Exp $
039: */
040: public class UtilReader {
041:
042: private static final Logger log = Logging
043: .getLoggerInstance(UtilReader.class);
044:
045: public static final String CONFIG_UTILS = "utils";
046:
047: /** Public ID of the Utilities config DTD version 1.0 */
048: public static final String PUBLIC_ID_UTIL_1_0 = "-//MMBase//DTD util config 1.0//EN";
049: /** DTD resource filename of the Utilities config DTD version 1.0 */
050: public static final String DTD_UTIL_1_0 = "util_1_0.dtd";
051:
052: /** Public ID of the most recent Utilities config DTD */
053: public static final String PUBLIC_ID_UTIL = PUBLIC_ID_UTIL_1_0;
054: /** DTD respource filename of the most recent Utilities config DTD */
055: public static final String DTD_UTIL = DTD_UTIL_1_0;
056:
057: /**
058: * Register the Public Ids for DTDs used by UtilReader
059: * This method is called by XMLEntityResolver.
060: */
061: public static void registerPublicIDs() {
062: XMLEntityResolver.registerPublicID(PUBLIC_ID_UTIL_1_0,
063: DTD_UTIL_1_0, UtilReader.class);
064: }
065:
066: private static final Map<String, UtilReader> utilReaders = new HashMap<String, UtilReader>(); // file-name -> utilreader
067:
068: /**
069: * Returns a UtilReader for the given fileName. When you use this, the UtilReader instance will be cached.
070: *
071: * @since MMBase-1.8
072: */
073:
074: public static UtilReader get(String fileName) {
075: UtilReader utilReader = utilReaders.get(fileName);
076: if (utilReader == null) {
077: synchronized (utilReaders) {
078: utilReader = new UtilReader(fileName);
079: utilReaders.put(fileName, utilReader);
080: }
081: }
082: return utilReader;
083: }
084:
085: static {
086: // doesnt startup, probably because of cyclic referecnes, if this happens in DocumentReader itself.
087: DocumentReader.utilProperties = UtilReader.get(
088: "documentreader.xml").getProperties();
089:
090: }
091:
092: private class UtilFileWatcher extends ResourceWatcher {
093: private ResourceWatcher wrappedWatcher;
094:
095: public UtilFileWatcher(ResourceWatcher f) {
096: super (); // true: keep reading.
097: wrappedWatcher = f;
098: }
099:
100: public void onChange(String f) {
101: readProperties(f);
102: if (wrappedWatcher != null) {
103: wrappedWatcher.onChange(f);
104: }
105: }
106: }
107:
108: private final Map<String, String> properties = new HashMap<String, String>();
109: private final Map<String, Collection<Map.Entry<String, String>>> maps = new HashMap<String, Collection<Map.Entry<String, String>>>();
110: private final ResourceWatcher watcher;
111: private final String file;
112:
113: /**
114: * Instantiates a UtilReader for a given configuration file in <config>/utils. If the configuration file is used on more spots, then you may consider
115: * using the static method {@link #get(String)} in stead.
116: *
117: * @param fileName The name of the property file (e.g. httppost.xml).
118: */
119: public UtilReader(String fileName) {
120: file = CONFIG_UTILS + "/" + fileName;
121: readProperties(file);
122: watcher = new UtilFileWatcher(null);
123: watcher.add(file);
124: watcher.start();
125:
126: }
127:
128: /**
129: * Produces a UtilReader for the given resource name.
130: * @param fileName a Resource name relative to config/utils
131: * @param w A unstarted ResourceWatcher without files. (It will be only be called from the
132: * filewatcher in this reader). It defines what must happen if something changes in the util's
133: * configuration. Since you probably don't need the resource name for that any more, you
134: * can also simply use {@link #UtilReader(String, Runnable)}
135: * @since MMBase-1.8
136: */
137: public UtilReader(String fileName, ResourceWatcher w) {
138: file = CONFIG_UTILS + "/" + fileName;
139: readProperties(file);
140: watcher = new UtilFileWatcher(w);
141: watcher.add(file);
142: watcher.start();
143:
144: }
145:
146: /**
147: * Produces a UtilReader for the given resource name.
148: * @param resourceName a Resource name relative to config/utils
149: * @param onChange A Runnable defining what must happen if something changes.
150: * @since MMBase-1.8
151: */
152: public UtilReader(String resourceName, final Runnable onChange) {
153: this (resourceName, new ResourceWatcher() {
154: public void onChange(String name) {
155: onChange.run();
156: }
157: });
158: }
159:
160: public void finalize() {
161: if (watcher != null)
162: watcher.exit();
163: }
164:
165: /**
166: * Get the properties of this utility.
167: */
168: public PropertiesMap<String> getProperties() {
169: return new PropertiesMap<String>(properties);
170: }
171:
172: /**
173: * Get the properties of this utility.
174: */
175: public PropertiesMap<Collection<Map.Entry<String, String>>> getMaps() {
176: return new PropertiesMap<Collection<Map.Entry<String, String>>>(
177: maps);
178: }
179:
180: /**
181: * Reports whether the configured resource (in the constructor) is actually backed. If not,
182: * getProperties will certainly return an empty Map.
183: * @since MMBase-1.8.1
184: */
185: public boolean resourceAvailable() {
186: try {
187: return ResourceLoader.getConfigurationRoot().getResource(
188: file).openConnection().getDoInput();
189: } catch (IOException io) {
190: return false;
191: }
192: }
193:
194: protected void readProperties(String s) {
195: properties.clear();
196: maps.clear();
197:
198: ResourceLoader configLoader = ResourceLoader
199: .getConfigurationRoot();
200: List<URL> configList = configLoader.getResourceList(s);
201: for (URL url : configList) {
202: org.xml.sax.InputSource is;
203: try {
204: is = ResourceLoader.getInputSource(url);
205: } catch (IOException ioe) {
206: // input source does not exist
207: log.debug(ioe.getMessage() + " for " + url);
208: continue;
209: }
210: if (is != null) {
211: log.debug("Reading " + url);
212: DocumentReader reader = new DocumentReader(is,
213: UtilReader.class);
214: Element e = reader.getElementByPath("util.properties");
215: if (e != null) {
216: for (Element p : reader.getChildElements(e,
217: "property")) {
218: String name = reader.getElementAttributeValue(
219: p, "name");
220: String type = reader.getElementAttributeValue(
221: p, "type");
222: if (type.equals("map")) {
223: Collection<Map.Entry<String, String>> entryList = new ArrayList<Map.Entry<String, String>>();
224:
225: for (Element entry : reader
226: .getChildElements(p, "entry")) {
227: String key = null;
228: String value = null;
229:
230: for (Element keyorvalue : reader
231: .getChildElements(entry, "*")) {
232: if (keyorvalue.getTagName().equals(
233: "key")) {
234: key = reader
235: .getElementValue(keyorvalue);
236: } else {
237: value = reader
238: .getNodeTextValue(
239: keyorvalue,
240: false);
241: }
242: }
243: if (key != null) {
244: entryList
245: .add(new Entry<String, String>(
246: key, value));
247: }
248: }
249: if (maps.containsKey(name)) {
250: log.service("Property '" + name + "' ("
251: + entryList + ") of " + url
252: + " is shadowed");
253: } else {
254: maps.put(name, entryList);
255: }
256: } else {
257: String value = reader.getElementValue(p);
258: if (properties.containsKey(name)) {
259: log.service("Property '" + name
260: + "' ('" + value + "') of "
261: + url + " is shadowed");
262: } else {
263: properties.put(name, value);
264: }
265: }
266: }
267: }
268: } else {
269: log.debug("Resource " + s + " does not exist");
270: }
271: }
272: if (properties.size() == 0) {
273: log.service("No properties read from " + configList);
274: } else {
275: log.service("Read " + properties.entrySet() + " from "
276: + configList);
277: }
278: }
279:
280: /**
281: * A unmodifiable Map, with extra 'Properties'-like methods. The entries of this Map are
282: * typically backed by the resources of an UtilReader (and the Map dynamically changes if the
283: * resources change).
284: * @since MMBase-1.8
285: */
286:
287: public static class PropertiesMap<E> extends AbstractMap<String, E> {
288:
289: private final Map<String, E> wrappedMap;
290:
291: /**
292: * Creates an empty Map (not very useful since this Map is unmodifiable).
293: */
294: public PropertiesMap() {
295: wrappedMap = new HashMap<String, E>();
296: }
297:
298: /**
299: * Wrapping the given map.
300: */
301: public PropertiesMap(Map<String, E> map) {
302: wrappedMap = map;
303: }
304:
305: /**
306: * {@inheritDoc}
307: */
308: public Set<Map.Entry<String, E>> entrySet() {
309: return new EntrySet();
310:
311: }
312:
313: /**
314: * Returns the object mapped with 'key', or defaultValue if there is none.
315: */
316: public E getProperty(String key, E defaultValue) {
317: E result = get(key);
318: return result == null ? defaultValue : result;
319: }
320:
321: private class EntrySet extends
322: AbstractSet<Map.Entry<String, E>> {
323: EntrySet() {
324: }
325:
326: public int size() {
327: return PropertiesMap.this .wrappedMap.size();
328: }
329:
330: public Iterator<Map.Entry<String, E>> iterator() {
331: return new EntrySetIterator();
332: }
333: }
334:
335: private class EntrySetIterator implements
336: Iterator<Map.Entry<String, E>> {
337: private Iterator<Map.Entry<String, E>> i;
338:
339: EntrySetIterator() {
340: i = PropertiesMap.this .wrappedMap.entrySet().iterator();
341: }
342:
343: public boolean hasNext() {
344: return i.hasNext();
345: }
346:
347: public Map.Entry<String, E> next() {
348: return i.next();
349: }
350:
351: public void remove() {
352: throw new UnsupportedOperationException("Unmodifiable");
353: }
354: }
355: }
356:
357: }
|