001: /*
002: * Helma License Notice
003: *
004: * The contents of this file are subject to the Helma License
005: * Version 2.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://adele.helma.org/download/helma/license.txt
008: *
009: * Copyright 1998-2003 Helma Software. All Rights Reserved.
010: *
011: * $RCSfile$
012: * $Author: root $
013: * $Revision: 8604 $
014: * $Date: 2007-09-28 15:16:38 +0200 (Fre, 28 Sep 2007) $
015: */
016:
017: package helma.util;
018:
019: import java.io.IOException;
020: import java.util.*;
021: import helma.framework.core.*;
022: import helma.framework.repository.Resource;
023: import helma.framework.repository.Repository;
024:
025: /**
026: * A property dictionary that is updated from property resources
027: */
028: public class ResourceProperties extends Properties {
029:
030: // Delay between checks
031: private final long CACHE_TIME = 1500L;
032:
033: // Default properties. Note that in contrast to java.util.Properties,
034: // defaultProperties are copied statically to ourselves in update(), so
035: // there's no need to check them in retrieval methods.
036: protected ResourceProperties defaultProperties;
037:
038: // Defines wether keys are case-sensitive or not
039: private boolean ignoreCase = true;
040:
041: // Cached checksum of last check
042: private long lastChecksum = 0;
043:
044: // Time of last check
045: private long lastCheck = 0;
046:
047: // Time porperties were last modified
048: private long lastModified = 0;
049:
050: // Application where to fetch additional resources
051: private Application app;
052:
053: // Name of possible resources to fetch from the applications's repositories
054: private String resourceName;
055:
056: // Sorted map of resources
057: private Set resources;
058:
059: // lower case key to original key mapping for case insensitive lookups
060: private Properties keyMap = new Properties();
061:
062: /**
063: * Constructs an empty ResourceProperties
064: * Resources must be added manually afterwards
065: */
066: public ResourceProperties() {
067: // TODO: we can't use TreeSet because we don't have the app's resource comparator
068: // Since resources don't implement Comparable, we can't add them to a "naked" TreeSet
069: // As a result, resource ordering is random when updating.
070: resources = new HashSet();
071: }
072:
073: /**
074: * Constructs an empty ResourceProperties
075: * Resources must be added manually afterwards
076: */
077: public ResourceProperties(Application app) {
078: resources = new TreeSet(app.getResourceComparator());
079: }
080:
081: /**
082: * Constructs a ResourceProperties retrieving resources from the given
083: * application using the given name to fetch resources
084: * @param app application to fetch resources from
085: * @param resourceName name to use when fetching resources from the application
086: */
087: public ResourceProperties(Application app, String resourceName) {
088: this .app = app;
089: this .resourceName = resourceName;
090: resources = new TreeSet(app.getResourceComparator());
091: }
092:
093: /**
094: * Constructs a ResourceProperties retrieving resources from the given
095: * application using the given name to fetch resources and falling back
096: * to the given default properties
097: * @param app application to fetch resources from
098: * @param resourceName name to use when fetching resources from the application
099: * @param defaultProperties default properties
100: */
101: public ResourceProperties(Application app, String resourceName,
102: ResourceProperties defaultProperties) {
103: this (app, resourceName);
104: this .defaultProperties = defaultProperties;
105: forceUpdate();
106: }
107:
108: /**
109: * Constructs a ResourceProperties retrieving resources from the given
110: * application using the given name to fetch resources and falling back
111: * to the given default properties
112: * @param app application to fetch resources from
113: * @param resourceName name to use when fetching resources from the application
114: * @param defaultProperties default properties
115: * @param ignoreCase ignore case for property keys, setting all keys to lower case
116: */
117: public ResourceProperties(Application app, String resourceName,
118: ResourceProperties defaultProperties, boolean ignoreCase) {
119: this (app, resourceName);
120: this .defaultProperties = defaultProperties;
121: this .ignoreCase = ignoreCase;
122: forceUpdate();
123: }
124:
125: /**
126: * Updates the properties regardless of an actual need
127: */
128: private void forceUpdate() {
129: lastChecksum = -1;
130: update();
131: }
132:
133: /**
134: * Sets the default properties and updates all properties
135: * @param defaultProperties default properties
136: */
137: public void setDefaultProperties(
138: ResourceProperties defaultProperties) {
139: this .defaultProperties = defaultProperties;
140: update();
141: }
142:
143: /**
144: * Adds a resource to the list of resources and updates all properties if
145: * needed
146: * @param resource resource to add
147: */
148: public void addResource(Resource resource) {
149: if (resource != null && !resources.contains(resource)) {
150: resources.add(resource);
151: forceUpdate();
152: }
153: }
154:
155: /**
156: * Removes a resource from the list of resources and updates all properties
157: * if needed
158: * @param resource resource to remove
159: */
160: public void removeResource(Resource resource) {
161: if (resources.contains(resource)) {
162: resources.remove(resource);
163: forceUpdate();
164: }
165: }
166:
167: /**
168: * Get an iterator over the properties' resources
169: * @return iterator over the properties' resources
170: */
171: public Iterator getResources() {
172: return resources.iterator();
173: }
174:
175: /**
176: * Updates all properties if there is a need to update
177: */
178: public synchronized void update() {
179: // set lastCheck first to reduce risk of recursive calls
180: lastCheck = System.currentTimeMillis();
181: if (getChecksum() != lastChecksum) {
182: // First collect properties into a temporary collection,
183: // in a second step copy over new properties,
184: // and in the final step delete properties which have gone.
185: ResourceProperties temp = new ResourceProperties();
186: temp.setIgnoreCase(ignoreCase);
187:
188: // first of all, properties are load from default properties
189: if (defaultProperties != null) {
190: defaultProperties.update();
191: temp.putAll(defaultProperties);
192: }
193:
194: // next we try to load properties from the application's
195: // repositories, if we belong to any application
196: if (resourceName != null) {
197: Iterator iterator = app.getRepositories().iterator();
198: while (iterator.hasNext()) {
199: try {
200: Repository repository = (Repository) iterator
201: .next();
202: Resource res = repository
203: .getResource(resourceName);
204: if (res != null && res.exists()) {
205: temp.load(res.getInputStream());
206: }
207: } catch (IOException iox) {
208: iox.printStackTrace();
209: }
210: }
211: }
212:
213: // at last we try to load properties from the resource list
214: if (resources != null) {
215: Iterator iterator = resources.iterator();
216: while (iterator.hasNext()) {
217: try {
218: Resource res = (Resource) iterator.next();
219: if (res.exists()) {
220: temp.load(res.getInputStream());
221: }
222: } catch (IOException iox) {
223: iox.printStackTrace();
224: }
225: }
226: }
227:
228: // Copy over new properties ...
229: putAll(temp);
230: // ... and remove properties which have been removed.
231: Iterator it = super .keySet().iterator();
232: while (it.hasNext()) {
233: if (!temp.containsKey(it.next())) {
234: it.remove();
235: }
236: }
237: // copy new up-to-date keyMap to ourself
238: keyMap = temp.keyMap;
239:
240: lastChecksum = getChecksum();
241: lastCheck = lastModified = System.currentTimeMillis();
242: }
243: }
244:
245: /**
246: * Extract all entries where the key matches the given string prefix from
247: * the source map to the target map, cutting off the prefix from the original key.
248: * The ignoreCase property is inherited and also considered when matching keys
249: * against the prefix.
250: *
251: * @param prefix the string prefix to match against
252: */
253: public ResourceProperties getSubProperties(String prefix) {
254: if (prefix == null)
255: throw new NullPointerException("prefix");
256: if ((System.currentTimeMillis() - lastCheck) > CACHE_TIME) {
257: update();
258: }
259: ResourceProperties subprops = new ResourceProperties();
260: subprops.setIgnoreCase(ignoreCase);
261: Iterator it = entrySet().iterator();
262: int prefixLength = prefix.length();
263: while (it.hasNext()) {
264: Map.Entry entry = (Map.Entry) it.next();
265: String key = entry.getKey().toString();
266: if (key.regionMatches(ignoreCase, 0, prefix, 0,
267: prefixLength)) {
268: subprops.put(key.substring(prefixLength), entry
269: .getValue());
270: }
271: }
272: return subprops;
273: }
274:
275: /**
276: * Checks wether the given object is in the value list
277: * @param value value to look for
278: * @return true if the value is found in the value list
279: */
280: public boolean contains(Object value) {
281: if ((System.currentTimeMillis() - lastCheck) > CACHE_TIME) {
282: update();
283: }
284: return super .contains(value.toString());
285: }
286:
287: /**
288: * Checks wether the given object is in the key list
289: * @param key key to look for
290: * @return true if the key is found in the key list
291: */
292: public boolean containsKey(Object key) {
293: if ((System.currentTimeMillis() - lastCheck) > CACHE_TIME) {
294: update();
295: }
296: if (ignoreCase) {
297: return keyMap.containsKey(key.toString().toLowerCase());
298: } else {
299: return super .containsKey(key.toString());
300: }
301: }
302:
303: /**
304: * Returns an enumeration of all values
305: * @return values enumeration
306: */
307: public Enumeration elements() {
308: if ((System.currentTimeMillis() - lastCheck) > CACHE_TIME) {
309: update();
310: }
311: return super .elements();
312: }
313:
314: /**
315: * Returns a value in this list fetched by the given key
316: * @param key key to use for fetching the value
317: * @return value belonging to the given key
318: */
319: public Object get(Object key) {
320: if ((System.currentTimeMillis() - lastCheck) > CACHE_TIME) {
321: update();
322: }
323: String strkey = key.toString();
324: if (ignoreCase) {
325: strkey = keyMap.getProperty(strkey.toLowerCase());
326: if (strkey == null)
327: return null;
328: }
329: return (String) super .get(strkey);
330: }
331:
332: /**
333: * Returns the date the resources were last modified
334: * @return last modified date
335: */
336: public long lastModified() {
337: if ((System.currentTimeMillis() - lastCheck) > CACHE_TIME) {
338: update();
339: }
340: return lastModified;
341: }
342:
343: /**
344: * Returns a checksum for all resources
345: * @return checksum
346: */
347: public long getChecksum() {
348: long checksum = 0;
349:
350: if (resourceName != null) {
351: Iterator iterator = app.getRepositories().iterator();
352: while (iterator.hasNext()) {
353: Repository repository = (Repository) iterator.next();
354: Resource resource = repository
355: .getResource(resourceName);
356: if (resource != null) {
357: checksum += resource.lastModified();
358: }
359: }
360: }
361:
362: if (resources != null) {
363: Iterator iterator = resources.iterator();
364: while (iterator.hasNext()) {
365: checksum += ((Resource) iterator.next()).lastModified();
366: }
367: }
368:
369: if (defaultProperties != null) {
370: checksum += defaultProperties.getChecksum();
371: }
372:
373: return checksum;
374: }
375:
376: /**
377: * Returns a value in the list fetched by the given key or a default value
378: * if no corresponding key is found
379: * @param key key to use for fetching the value
380: * @param defaultValue default value to return if key is not found
381: * @return spiecific value or default value if not found
382: */
383: public String getProperty(String key, String defaultValue) {
384: if ((System.currentTimeMillis() - lastCheck) > CACHE_TIME) {
385: update();
386: }
387: if (ignoreCase) {
388: key = keyMap.getProperty(key.toLowerCase());
389: if (key == null)
390: return defaultValue;
391: }
392: return super .getProperty(key, defaultValue);
393: }
394:
395: /**
396: * Returns a value in this list fetched by the given key
397: * @param key key to use for fetching the value
398: * @return value belonging to the given key
399: */
400: public String getProperty(String key) {
401: if ((System.currentTimeMillis() - lastCheck) > CACHE_TIME) {
402: update();
403: }
404: if (ignoreCase) {
405: key = keyMap.getProperty(key.toLowerCase());
406: if (key == null)
407: return null;
408: }
409: return super .getProperty(key);
410: }
411:
412: /**
413: * Checks wether the properties list is empty
414: * @return true if the properties list is empty
415: */
416: public boolean isEmpty() {
417: if ((System.currentTimeMillis() - lastCheck) > CACHE_TIME) {
418: update();
419: }
420: return super .isEmpty();
421: }
422:
423: /**
424: * Checks wether case-sensitivity is ignored for keys
425: * @return true if case-sensitivity is ignored for keys
426: */
427: public boolean isIgnoreCase() {
428: return ignoreCase;
429: }
430:
431: /**
432: * Returns an enumeration of all keys
433: * @return keys enumeration
434: */
435: public Enumeration keys() {
436: if ((System.currentTimeMillis() - lastCheck) > CACHE_TIME) {
437: update();
438: }
439: return super .keys();
440: }
441:
442: /**
443: * Returns a set of all keys
444: * @return keys set
445: */
446: public Set keySet() {
447: if ((System.currentTimeMillis() - lastCheck) > CACHE_TIME) {
448: update();
449: }
450: return super .keySet();
451: }
452:
453: /**
454: * Puts a new key-value pair into the properties list
455: * @param key key
456: * @param value value
457: * @return the old value, if an old value got replaced
458: */
459: public Object put(Object key, Object value) {
460: if (value != null) {
461: value = value.toString().trim();
462: }
463: String strkey = key.toString();
464: if (ignoreCase) {
465: keyMap.put(strkey.toLowerCase(), strkey);
466: }
467: return super .put(strkey, value);
468: }
469:
470: /**
471: * Removes a key-value pair from the properties list
472: * @param key key
473: * @return the old value
474: */
475: public Object remove(Object key) {
476: String strkey = key.toString();
477: if (ignoreCase) {
478: strkey = (String) keyMap.remove(strkey.toLowerCase());
479: if (strkey == null)
480: return null;
481: }
482: return super .remove(strkey);
483: }
484:
485: /**
486: * Changes how keys are handled
487: * @param ignore true if to ignore case-sensitivity for keys
488: */
489: public void setIgnoreCase(boolean ignore) {
490: if (!super .isEmpty()) {
491: throw new RuntimeException(
492: "setIgnoreCase() can only be called on empty Properties");
493: }
494: ignoreCase = ignore;
495: }
496:
497: /**
498: * Returns the number of peroperties in the list
499: * @return number of properties
500: */
501: public int size() {
502: if ((System.currentTimeMillis() - lastCheck) > CACHE_TIME) {
503: update();
504: }
505: return super .size();
506: }
507:
508: /**
509: * Overwrite clear() to also empty the key map.
510: */
511: public void clear() {
512: keyMap.clear();
513: super .clear();
514: }
515:
516: /**
517: * Returns a string-representation of the class
518: * @return string
519: */
520: public String toString() {
521: return super.toString();
522: }
523:
524: }
|