001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.wicket.resource;
018:
019: import java.io.BufferedInputStream;
020: import java.io.IOException;
021: import java.util.ArrayList;
022: import java.util.Enumeration;
023: import java.util.Iterator;
024: import java.util.List;
025: import java.util.Map;
026:
027: import org.apache.wicket.Application;
028: import org.apache.wicket.settings.IResourceSettings;
029: import org.apache.wicket.util.concurrent.ConcurrentHashMap;
030: import org.apache.wicket.util.io.Streams;
031: import org.apache.wicket.util.listener.IChangeListener;
032: import org.apache.wicket.util.resource.IFixedLocationResourceStream;
033: import org.apache.wicket.util.resource.IResourceStream;
034: import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
035: import org.apache.wicket.util.resource.locator.IResourceStreamLocator;
036: import org.apache.wicket.util.string.Strings;
037: import org.apache.wicket.util.value.ValueMap;
038: import org.apache.wicket.util.watch.ModificationWatcher;
039: import org.slf4j.Logger;
040: import org.slf4j.LoggerFactory;
041:
042: /**
043: * Default implementation of {@link IPropertiesFactory} which uses the
044: * {@link IResourceStreamLocator} as defined by {@link IResourceSettings#getResourceStreamLocator()}
045: * to load the {@link Properties} objects. Depending on the settings, it will assign
046: * {@link ModificationWatcher}s to the loaded resources to support reloading.
047: *
048: * @see org.apache.wicket.settings.IResourceSettings#getPropertiesFactory()
049: *
050: * @author Juergen Donnerstag
051: */
052: public class PropertiesFactory implements IPropertiesFactory {
053: /** Log. */
054: private static final Logger log = LoggerFactory
055: .getLogger(PropertiesFactory.class);
056:
057: /**
058: * Listeners will be invoked after changes to property file have been detected
059: */
060: private final List afterReloadListeners = new ArrayList();
061:
062: /** Cache for all property files loaded */
063: private final Map propertiesCache = new ConcurrentHashMap();
064:
065: /** Application */
066: private final Application application;
067:
068: /**
069: * Construct.
070: *
071: * @param application
072: * Application for this properties factory.
073: */
074: public PropertiesFactory(Application application) {
075: this .application = application;
076: }
077:
078: /**
079: * @see org.apache.wicket.resource.IPropertiesFactory#addListener(org.apache.wicket.resource.IPropertiesChangeListener)
080: */
081: public void addListener(final IPropertiesChangeListener listener) {
082: // Make sure listeners are added only once
083: if (afterReloadListeners.contains(listener) == false) {
084: afterReloadListeners.add(listener);
085: }
086: }
087:
088: /**
089: * @see org.apache.wicket.resource.IPropertiesFactory#clearCache()
090: */
091: public final void clearCache() {
092: propertiesCache.clear();
093: }
094:
095: /**
096: *
097: * @see org.apache.wicket.resource.IPropertiesFactory#load(java.lang.Class, java.lang.String)
098: */
099: public Properties load(final Class clazz, final String path) {
100: // Check the cache
101: Properties properties = (Properties) propertiesCache.get(path);
102: if (properties != null) {
103: // Return null, if no resource stream was found
104: if (properties == Properties.EMPTY_PROPERTIES) {
105: properties = null;
106: }
107: return properties;
108: }
109:
110: // If not in the cache than try to load the resource stream
111: IResourceStream stream = application.getResourceSettings()
112: .getResourceStreamLocator().locate(clazz, path);
113: if (stream != null) {
114: // Load the properties from the stream
115: properties = loadPropertiesFileAndWatchForChanges(path,
116: stream);
117: if (properties != null) {
118: propertiesCache.put(path, properties);
119: return properties;
120: }
121: }
122:
123: // Add a placeholder to the cache. Null is not a valid value to add.
124: propertiesCache.put(path, Properties.EMPTY_PROPERTIES);
125: return null;
126: }
127:
128: /**
129: * Helper method to do the actual loading of resources if required.
130: *
131: * @param key
132: * The key for the resource
133: * @param resourceStream
134: * The properties file stream to load and begin to watch
135: * @return The map of loaded resources
136: */
137: private synchronized Properties loadPropertiesFile(
138: final String key, final IResourceStream resourceStream) {
139: // Make sure someone else didn't load our resources while we were
140: // waiting for the synchronized lock on the method
141: Properties props = (Properties) propertiesCache.get(key);
142: if (props != null) {
143: return props;
144: }
145:
146: if (resourceStream == null) {
147: props = new Properties(key, ValueMap.EMPTY_MAP);
148: } else {
149: ValueMap strings = null;
150:
151: try {
152: try {
153: // Get the InputStream
154: BufferedInputStream in = new BufferedInputStream(
155: resourceStream.getInputStream());
156:
157: // Determine if resource is a XML File
158: boolean loadAsXml = false;
159: if (resourceStream instanceof IFixedLocationResourceStream) {
160: String location = ((IFixedLocationResourceStream) resourceStream)
161: .locationAsString();
162: if (location != null) {
163: String ext = Strings.lastPathComponent(
164: location, '.').toLowerCase();
165: if ("xml".equals(ext)) {
166: loadAsXml = true;
167: }
168: }
169: }
170:
171: // Load the properties
172: java.util.Properties properties = new java.util.Properties();
173: if (loadAsXml) {
174: Streams.loadFromXml(properties, in);
175: } else {
176: properties.load(in);
177: }
178:
179: // Copy the properties into the ValueMap
180: strings = new ValueMap();
181: Enumeration enumeration = properties
182: .propertyNames();
183: while (enumeration.hasMoreElements()) {
184: String property = (String) enumeration
185: .nextElement();
186: strings.put(property, properties
187: .getProperty(property));
188: }
189: } finally {
190: resourceStream.close();
191: }
192: } catch (ResourceStreamNotFoundException e) {
193: log
194: .warn("Unable to find resource "
195: + resourceStream, e);
196: strings = ValueMap.EMPTY_MAP;
197: } catch (IOException e) {
198: log.warn("Unable to access resource " + resourceStream,
199: e);
200: strings = ValueMap.EMPTY_MAP;
201: }
202:
203: props = new Properties(key, strings);
204: }
205:
206: return props;
207: }
208:
209: /**
210: * Load properties file from an IResourceStream and add an {@link IChangeListener}to the
211: * {@link ModificationWatcher} so that if the resource changes, we can reload it automatically.
212: *
213: * @param key
214: * The key for the resource
215: * @param resourceStream
216: * The properties file stream to load and begin to watch
217: * @return The map of loaded resources
218: */
219: private final Properties loadPropertiesFileAndWatchForChanges(
220: final String key, final IResourceStream resourceStream) {
221: // Watch file modifications
222: final ModificationWatcher watcher = application
223: .getResourceSettings().getResourceWatcher(true);
224: if (watcher != null) {
225: watcher.add(resourceStream, new IChangeListener() {
226: public void onChange() {
227: log
228: .info("A properties files has changed. Removing all entries "
229: + "from the cache. Resource: "
230: + resourceStream);
231:
232: // Clear the whole cache as associated localized files may
233: // be affected and may need reloading as well.
234: clearCache();
235:
236: // clear the localizer cache as well
237: application.getResourceSettings().getLocalizer()
238: .clearCache();
239:
240: // Inform all listeners
241: Iterator iter = afterReloadListeners.iterator();
242: while (iter.hasNext()) {
243: IPropertiesChangeListener listener = (IPropertiesChangeListener) iter
244: .next();
245: try {
246: listener.propertiesChanged(key);
247: } catch (Throwable ex) {
248: log
249: .error("PropertiesReloadListener has thrown an exception: "
250: + ex.getMessage());
251: }
252: }
253: }
254: });
255: }
256:
257: log.info("Loading properties files from " + resourceStream);
258: return loadPropertiesFile(key, resourceStream);
259: }
260:
261: /**
262: * For subclasses to get access to the cache
263: *
264: * @return Map
265: */
266: protected final Map getCache() {
267: return propertiesCache;
268: }
269: }
|