001: /*
002: * $Id: ComponentStringResourceLoader.java,v 1.5 2005/01/19 08:07:57
003: * jonathanlocke Exp $ $Revision: 459415 $ $Date: 2006-02-17 21:51:27 +0100 (Fri, 17 Feb 2006) $
004: *
005: * ==============================================================================
006: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
007: * use this file except in compliance with the License. You may obtain a copy of
008: * the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
014: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
015: * License for the specific language governing permissions and limitations under
016: * the License.
017: */
018: package wicket.resource;
019:
020: import java.io.BufferedInputStream;
021: import java.io.IOException;
022: import java.util.ArrayList;
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.Locale;
027: import java.util.Map;
028:
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031:
032: import wicket.Application;
033: import wicket.Component;
034: import wicket.util.listener.IChangeListener;
035: import wicket.util.resource.IResourceStream;
036: import wicket.util.resource.ResourceStreamNotFoundException;
037: import wicket.util.string.AppendingStringBuffer;
038: import wicket.util.value.ValueMap;
039: import wicket.util.watch.ModificationWatcher;
040:
041: /**
042: * Reloadable properties. It is not a 100% replacement for java.util.Properties
043: * as it does not provide the same interface. But is serves kind of the same
044: * purpose with Wicket specific features. PropertiesFactory actually loads and
045: * reloads the Properties and mataince a cache. Hence properties files are
046: * loaded just once.
047: * <p>
048: *
049: * @see wicket.settings.IResourceSettings#getPropertiesFactory()
050: *
051: * @author Juergen Donnerstag
052: */
053: public class PropertiesFactory implements IPropertiesFactory {
054: /** Log. */
055: private static final Log log = LogFactory
056: .getLog(PropertiesFactory.class);
057:
058: /** Cache for all properties files loaded */
059: private final Map propertiesCache = new HashMap();
060:
061: /** Listeners will be invoked after properties have been reloaded */
062: private final List afterReloadListeners = new ArrayList();
063:
064: /**
065: * Construct.
066: */
067: public PropertiesFactory() {
068: }
069:
070: /**
071: * @see wicket.resource.IPropertiesFactory#addListener(wicket.resource.IPropertiesReloadListener)
072: */
073: public void addListener(final IPropertiesReloadListener listener) {
074: // Make sure listeners are added only once
075: if (afterReloadListeners.contains(listener) == false) {
076: afterReloadListeners.add(listener);
077: }
078: }
079:
080: /**
081: * @see wicket.resource.IPropertiesFactory#get(wicket.Application, java.lang.Class, java.lang.String, java.util.Locale)
082: */
083: public Properties get(final Application application,
084: final Class clazz, final String style, final Locale locale) {
085: final String key = createResourceKey(clazz, locale, style);
086: Properties props = (Properties) propertiesCache.get(key);
087: if ((props == null)
088: && (propertiesCache.containsKey(key) == false)) {
089: final IResourceStream resource = application
090: .getResourceSettings().getResourceStreamLocator()
091: .locate(clazz, clazz.getName().replace('.', '/'),
092: style, locale, "properties");
093:
094: if (resource != null) {
095: props = loadPropertiesFileAndWatchForChanges(key,
096: resource, clazz, style, locale);
097: }
098:
099: // add the markup to the cache
100: synchronized (propertiesCache) {
101: propertiesCache.put(key, props);
102: }
103: }
104:
105: return props;
106: }
107:
108: /**
109: * For subclasses to get access to the cache
110: *
111: * @return Map
112: */
113: protected final Map getCache() {
114: return this .propertiesCache;
115: }
116:
117: /**
118: * @see wicket.resource.IPropertiesFactory#clearCache()
119: */
120: public final void clearCache() {
121: propertiesCache.clear();
122: }
123:
124: /**
125: * Create a unique key to identify the properties file in the cache
126: *
127: * @param componentClass
128: * The class that resources are bring loaded for
129: * @param locale
130: * The locale to load reosurces for
131: * @param style
132: * The style to load resources for (see {@link wicket.Session})
133: * @return The resource key
134: */
135: public final String createResourceKey(final Class componentClass,
136: final Locale locale, final String style) {
137: final AppendingStringBuffer buffer = new AppendingStringBuffer(
138: 80);
139: if (componentClass != null) {
140: buffer.append(componentClass.getName());
141: }
142: if (style != null) {
143: buffer.append(Component.PATH_SEPARATOR);
144: buffer.append(style);
145: }
146: if (locale != null) {
147: buffer.append(Component.PATH_SEPARATOR);
148: boolean l = locale.getLanguage().length() != 0;
149: boolean c = locale.getCountry().length() != 0;
150: boolean v = locale.getVariant().length() != 0;
151: buffer.append(locale.getLanguage());
152: if (c || (l && v)) {
153: // This may just append '_'
154: buffer.append('_').append(locale.getCountry());
155: }
156: if (v && (l || c)) {
157: buffer.append('_').append(locale.getVariant());
158: }
159: }
160:
161: final String id = buffer.toString();
162: return id;
163: }
164:
165: /**
166: * Helper method to do the actual loading of resources if required.
167: *
168: * @param key
169: * The key for the resource
170: * @param resourceStream
171: * The properties file stream to load and begin to watch
172: * @param componentClass
173: * The class that resources are bring loaded for
174: * @param style
175: * The style to load resources for (see {@link wicket.Session})
176: * @param locale
177: * The locale to load reosurces for
178: * @return The map of loaded resources
179: */
180: private synchronized Properties loadPropertiesFile(
181: final String key, final IResourceStream resourceStream,
182: final Class componentClass, final String style,
183: final Locale locale) {
184: // Make sure someone else didn't load our resources while we were
185: // waiting for the synchronized lock on the method
186: Properties props = (Properties) propertiesCache.get(key);
187: if (props != null) {
188: return props;
189: }
190:
191: // Do the resource load
192: final java.util.Properties properties = new java.util.Properties();
193:
194: if (resourceStream == null) {
195: props = new Properties(key, ValueMap.EMPTY_MAP);
196: } else {
197: ValueMap strings = ValueMap.EMPTY_MAP;
198:
199: try {
200: try {
201: properties.load(new BufferedInputStream(
202: resourceStream.getInputStream()));
203: strings = new ValueMap(properties);
204: } finally {
205: resourceStream.close();
206: }
207: } catch (ResourceStreamNotFoundException e) {
208: log
209: .warn("Unable to find resource "
210: + resourceStream, e);
211: strings = ValueMap.EMPTY_MAP;
212: } catch (IOException e) {
213: log.warn("Unable to access resource " + resourceStream,
214: e);
215: strings = ValueMap.EMPTY_MAP;
216: }
217:
218: props = new Properties(key, strings);
219: }
220:
221: return props;
222: }
223:
224: /**
225: * Load properties file from an IResourceStream and add an
226: * {@link IChangeListener}to the {@link ModificationWatcher} so that if the
227: * resource changes, we can reload it automatically.
228: *
229: * @param key
230: * The key for the resource
231: * @param resourceStream
232: * The properties file stream to load and begin to watch
233: * @param componentClass
234: * The class that resources are bring loaded for
235: * @param style
236: * The style to load resources for (see {@link wicket.Session})
237: * @param locale
238: * The locale to load reosurces for
239: * @return The map of loaded resources
240: */
241: private final Properties loadPropertiesFileAndWatchForChanges(
242: final String key, final IResourceStream resourceStream,
243: final Class componentClass, final String style,
244: final Locale locale) {
245: // Watch file modifications
246: final ModificationWatcher watcher = Application.get()
247: .getResourceSettings().getResourceWatcher();
248: if (watcher != null) {
249: watcher.add(resourceStream, new IChangeListener() {
250: public void onChange() {
251: log
252: .info("A properties files has changed. Remove all entries from the cache. Resource: "
253: + resourceStream);
254:
255: // Clear the whole cache as associated localized files may
256: // be affected and may need reloading as well. We make it
257: // easy. Usually the watcher is activ in dev mode only anyway.
258: clearCache();
259:
260: // Inform all listeners
261: for (Iterator iter = afterReloadListeners
262: .iterator(); iter.hasNext();) {
263: IPropertiesReloadListener listener = (IPropertiesReloadListener) iter
264: .next();
265: try {
266: listener.propertiesLoaded(key);
267: } catch (Throwable ex) {
268: log
269: .error("PropertiesReloadListener throw an exception: "
270: + ex.getMessage());
271: }
272: }
273: }
274: });
275: }
276:
277: log.info("Loading properties files from " + resourceStream);
278: return loadPropertiesFile(key, resourceStream, componentClass,
279: style, locale);
280: }
281: }
|