wicket.resource.PropertiesFactory.java Source code

Java tutorial

Introduction

Here is the source code for wicket.resource.PropertiesFactory.java

Source

/*
 * $Id: ComponentStringResourceLoader.java,v 1.5 2005/01/19 08:07:57
 * jonathanlocke Exp $ $Revision: 7020 $ $Date: 2006-05-24 20:53:56 +0000 (Wed,
 * 24 May 2006) $
 * 
 * ==============================================================================
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package wicket.resource;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import wicket.Application;
import wicket.Component;
import wicket.settings.IResourceSettings;
import wicket.util.listener.IChangeListener;
import wicket.util.resource.IResourceStream;
import wicket.util.resource.ResourceStreamNotFoundException;
import wicket.util.string.AppendingStringBuffer;
import wicket.util.value.ValueMap;
import wicket.util.watch.ModificationWatcher;

/**
 * Reloadable properties. It is not a 100% replacement for java.util.Properties
 * as it does not provide the same interface. But is serves kind of the same
 * purpose with Wicket specific features. PropertiesFactory actually loads and
 * reloads the Properties and mataince a cache. Hence properties files are
 * loaded just once.
 * <p>
 * 
 * @see wicket.settings.IResourceSettings#getPropertiesFactory()
 * 
 * @author Juergen Donnerstag
 */
public class PropertiesFactory implements IPropertiesFactory {
    /** Log. */
    private static final Log log = LogFactory.getLog(PropertiesFactory.class);

    /** Cache for all properties files loaded */
    private final Map<String, Properties> propertiesCache = new HashMap<String, Properties>();

    /** Listeners will be invoked after properties have been reloaded */
    private final List<IPropertiesReloadListener> afterReloadListeners = new ArrayList<IPropertiesReloadListener>();

    /** Resource Settings */
    private final IResourceSettings resourceSettings;

    /**
     * Construct.
     * 
     * @param application
     */
    public PropertiesFactory(final Application application) {
        this.resourceSettings = application.getResourceSettings();
    }

    /**
     * @see wicket.resource.IPropertiesFactory#addListener(wicket.resource.IPropertiesReloadListener)
     */
    public void addListener(final IPropertiesReloadListener listener) {
        // Make sure listeners are added only once
        if (afterReloadListeners.contains(listener) == false) {
            afterReloadListeners.add(listener);
        }
    }

    /**
     * @see wicket.resource.IPropertiesFactory#get(java.lang.Class,
     *      java.lang.String, java.util.Locale)
     */
    public Properties get(final Class clazz, final String style, final Locale locale) {
        final String key = createResourceKey(clazz, locale, style);
        Properties props = propertiesCache.get(key);
        if ((props == null) && (propertiesCache.containsKey(key) == false)) {
            final IResourceStream resource = resourceSettings.getResourceStreamLocator().locate(clazz,
                    clazz.getName().replace('.', '/'), style, locale, "properties");

            if (resource != null) {
                props = loadPropertiesFileAndWatchForChanges(key, resource, clazz, style, locale);
            }

            // add the markup to the cache
            synchronized (propertiesCache) {
                propertiesCache.put(key, props);
            }
        }

        return props;
    }

    /**
     * For subclasses to get access to the cache
     * 
     * @return Map
     */
    protected final Map getCache() {
        return this.propertiesCache;
    }

    /**
     * @see wicket.resource.IPropertiesFactory#clearCache()
     */
    public final void clearCache() {
        propertiesCache.clear();
    }

    /**
     * Create a unique key to identify the properties file in the cache
     * 
     * @param componentClass
     *            The class that resources are bring loaded for
     * @param locale
     *            The locale to load reosurces for
     * @param style
     *            The style to load resources for (see {@link wicket.Session})
     * @return The resource key
     */
    public final String createResourceKey(final Class componentClass, final Locale locale, final String style) {
        final AppendingStringBuffer buffer = new AppendingStringBuffer(80);
        if (componentClass != null) {
            buffer.append(componentClass.getName());
        }
        if (style != null) {
            buffer.append(Component.PATH_SEPARATOR);
            buffer.append(style);
        }
        if (locale != null) {
            buffer.append(Component.PATH_SEPARATOR);
            boolean l = locale.getLanguage().length() != 0;
            boolean c = locale.getCountry().length() != 0;
            boolean v = locale.getVariant().length() != 0;
            buffer.append(locale.getLanguage());
            if (c || (l && v)) {
                // This may just append '_'
                buffer.append('_').append(locale.getCountry());
            }
            if (v && (l || c)) {
                buffer.append('_').append(locale.getVariant());
            }
        }

        final String id = buffer.toString();
        return id;
    }

    /**
     * Helper method to do the actual loading of resources if required.
     * 
     * @param key
     *            The key for the resource
     * @param resourceStream
     *            The properties file stream to load and begin to watch
     * @param componentClass
     *            The class that resources are bring loaded for
     * @param style
     *            The style to load resources for (see {@link wicket.Session})
     * @param locale
     *            The locale to load reosurces for
     * @return The map of loaded resources
     */
    private synchronized Properties loadPropertiesFile(final String key, final IResourceStream resourceStream,
            final Class componentClass, final String style, final Locale locale) {
        // Make sure someone else didn't load our resources while we were
        // waiting for the synchronized lock on the method
        Properties props = propertiesCache.get(key);
        if (props != null) {
            return props;
        }

        // Do the resource load
        final java.util.Properties properties = new java.util.Properties();

        if (resourceStream == null) {
            props = new Properties(key, ValueMap.EMPTY_MAP);
        } else {
            ValueMap strings = null;

            try {
                try {
                    properties.load(new BufferedInputStream(resourceStream.getInputStream()));
                    strings = new ValueMap();
                    Enumeration<?> enumeration = properties.propertyNames();
                    while (enumeration.hasMoreElements()) {
                        String property = (String) enumeration.nextElement();
                        strings.put(property, properties.getProperty(property));
                    }
                } finally {
                    resourceStream.close();
                }
            } catch (ResourceStreamNotFoundException e) {
                log.warn("Unable to find resource " + resourceStream, e);
                strings = ValueMap.EMPTY_MAP;
            } catch (IOException e) {
                log.warn("Unable to access resource " + resourceStream, e);
                strings = ValueMap.EMPTY_MAP;
            }

            props = new Properties(key, strings);
        }

        return props;
    }

    /**
     * Load properties file from an IResourceStream and add an
     * {@link IChangeListener}to the {@link ModificationWatcher} so that if the
     * resource changes, we can reload it automatically.
     * 
     * @param key
     *            The key for the resource
     * @param resourceStream
     *            The properties file stream to load and begin to watch
     * @param componentClass
     *            The class that resources are bring loaded for
     * @param style
     *            The style to load resources for (see {@link wicket.Session})
     * @param locale
     *            The locale to load reosurces for
     * @return The map of loaded resources
     */
    private final Properties loadPropertiesFileAndWatchForChanges(final String key,
            final IResourceStream resourceStream, final Class componentClass, final String style,
            final Locale locale) {
        // Watch file modifications
        final ModificationWatcher watcher = resourceSettings.getResourceWatcher(true);
        if (watcher != null) {
            watcher.add(resourceStream, new IChangeListener() {
                public void onChange() {
                    log.info("A properties files has changed. Remove all entries from the cache. Resource: "
                            + resourceStream);

                    // Clear the whole cache as associated localized files may
                    // be affected and may need reloading as well. We make it
                    // easy. Usually the watcher is activ in dev mode only
                    // anyway.
                    clearCache();

                    // Inform all listeners
                    for (Iterator iter = afterReloadListeners.iterator(); iter.hasNext();) {
                        IPropertiesReloadListener listener = (IPropertiesReloadListener) iter.next();
                        try {
                            listener.propertiesLoaded(key);
                        } catch (Throwable ex) {
                            log.error("PropertiesReloadListener throw an exception: " + ex.getMessage());
                        }
                    }
                }
            });
        }

        log.info("Loading properties files from " + resourceStream);
        return loadPropertiesFile(key, resourceStream, componentClass, style, locale);
    }
}