All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.wicket.Localizer Maven / Gradle / Ivy

Go to download

Wicket is a Java web application framework that takes simplicity, separation of concerns and ease of development to a whole new level. Wicket pages can be mocked up, previewed and later revised using standard WYSIWYG HTML design tools. Dynamic content processing and form handling is all handled in Java code using a first-class component model backed by POJO data beans that can easily be persisted using your favorite technology.

There is a newer version: 10.2.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.wicket;

import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.wicket.core.util.string.interpolator.ConvertingPropertyVariableInterpolator;
import org.apache.wicket.markup.repeater.AbstractRepeater;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.resource.loader.IStringResourceLoader;
import org.apache.wicket.settings.ResourceSettings;
import org.apache.wicket.util.lang.Generics;
import org.apache.wicket.util.string.AppendingStringBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * A utility class that encapsulates all of the localization related functionality in a way that it
 * can be accessed by all areas of the framework in a consistent way. A singleton instance of this
 * class is available via the Application object.
 * 

* You may register additional IStringResourceLoader to extend or replace Wickets default search * strategy for the properties. E.g. string resource loaders which load the properties from a * database. There should be hardly any need to extend Localizer. * * @see org.apache.wicket.settings.ResourceSettings#getLocalizer() * @see org.apache.wicket.resource.loader.IStringResourceLoader * @see org.apache.wicket.settings.ResourceSettings#getStringResourceLoaders() * * @author Chris Turner * @author Juergen Donnerstag */ public class Localizer { private static final Logger log = LoggerFactory.getLogger(Localizer.class); /** ConcurrentHashMap does not allow null values */ private static final String NULL_VALUE = ""; /** Cache properties */ private Map cache = newCache(); /** Database that maps class names to an integer id. */ private final ClassMetaDatabase metaDatabase = new ClassMetaDatabase(); /** * @return Same as Application.get().getResourceSettings().getLocalizer() */ public static Localizer get() { return Application.get().getResourceSettings().getLocalizer(); } /** * Create the utils instance class backed by the configuration information contained within the * supplied application object. */ public Localizer() { } /** * Clear all cache entries by instantiating a new cache object * * @see #newCache() */ public final void clearCache() { if (cache != null) { cache = newCache(); } } /** * @see #getString(String, Component, IModel, Locale, String, String) * * @param key * The key to obtain the resource for * @param component * The component to get the resource for * @return The string resource * @throws MissingResourceException * If resource not found and configuration dictates that exception should be thrown */ public String getString(final String key, final Component component) throws MissingResourceException { return getString(key, component, null, null, null, (String)null); } /** * @see #getString(String, Component, IModel, Locale, String, String) * * @param key * The key to obtain the resource for * @param component * The component to get the resource for * @param model * The model to use for property substitutions in the strings (optional) * @return The string resource * @throws MissingResourceException * If resource not found and configuration dictates that exception should be thrown */ public String getString(final String key, final Component component, final IModel model) throws MissingResourceException { return getString(key, component, model, null, null, (String)null); } /** * @see #getString(String, Component, IModel, Locale, String, String) * * @param key * The key to obtain the resource for * @param component * The component to get the resource for * @param defaultValue * The default value (optional) * @return The string resource * @throws MissingResourceException * If resource not found and configuration dictates that exception should be thrown */ public String getString(final String key, final Component component, final String defaultValue) throws MissingResourceException { return getString(key, component, null, null, null, defaultValue); } /** * @see #getString(String, Component, IModel, Locale, String, String) * * @param key * The key to obtain the resource for * @param component * The component to get the resource for * @param model * The model to use for property substitutions in the strings (optional) * @param defaultValue * The default value (optional) * @return The string resource * @throws MissingResourceException * If resource not found and configuration dictates that exception should be thrown */ public String getString(final String key, final Component component, final IModel model, final String defaultValue) throws MissingResourceException { return getString(key, component, model, null, null, defaultValue); } /** * Get the localized string using all of the supplied parameters. This method is left public to * allow developers full control over string resource loading. However, it is recommended that * one of the other convenience methods in the class are used as they handle all of the work * related to obtaining the current user locale and style information. * * @param key * The key to obtain the resource for * @param component * The component to get the resource for (optional) * @param model * The model to use for substitutions in the strings (optional) * @param locale * If != null, it'll supersede the component's locale * @param style * If != null, it'll supersede the component's style * @param defaultValue * The default value (optional) * @return The string resource * @throws MissingResourceException * If resource not found and configuration dictates that exception should be thrown */ public String getString(final String key, final Component component, final IModel model, final Locale locale, final String style, final String defaultValue) throws MissingResourceException { IModel defaultValueModel = defaultValue != null ? Model.of(defaultValue) : null; return getString(key, component, model, locale, style, defaultValueModel); } /** * Get the localized string using all of the supplied parameters. This method is left public to * allow developers full control over string resource loading. However, it is recommended that * one of the other convenience methods in the class are used as they handle all of the work * related to obtaining the current user locale and style information. * * @param key * The key to obtain the resource for * @param component * The component to get the resource for (optional) * @param model * The model to use for substitutions in the strings (optional) * @param locale * If != null, it'll supersede the component's locale * @param style * If != null, it'll supersede the component's style * @param defaultValue * The default value (optional) * @return The string resource * @throws MissingResourceException * If resource not found and configuration dictates that exception should be thrown */ public String getString(final String key, final Component component, final IModel model, final Locale locale, final String style, final IModel defaultValue) throws MissingResourceException { final ResourceSettings resourceSettings = Application.get().getResourceSettings(); String value = getStringIgnoreSettings(key, component, model, locale, style, null); // If a property value has been found, or a default value was given, // then replace the placeholder and we are done if (value != null) { return value; } else if (defaultValue != null && resourceSettings.getUseDefaultOnMissingResource()) { // Resource not found, so handle missing resources based on // application configuration and try the default value value = defaultValue.getObject(); if (value != null) { // If a property value has been found, or a default value was given, // then replace the placeholder and we are done return substitutePropertyExpressions(component, value, model); } } if (resourceSettings.getThrowExceptionOnMissingResource()) { AppendingStringBuffer message = new AppendingStringBuffer("Unable to find property: '"); message.append(key); message.append('\''); if (component != null) { message.append(" for component: "); message.append(component.getPageRelativePath()); message.append(" [class=").append(component.getClass().getName()).append(']'); } message.append(". Locale: ").append(locale).append(", style: ").append(style); throw new MissingResourceException(message.toString(), (component != null ? component.getClass().getName() : ""), key); } return "[Warning: Property for '" + key + "' not found]"; } /** * @see #getStringIgnoreSettings(String, Component, IModel, Locale, String, String) * * @param key * The key to obtain the resource for * @param component * The component to get the resource for (optional) * @param model * The model to use for substitutions in the strings (optional) * @param defaultValue * The default value (optional) * @return The string resource */ public String getStringIgnoreSettings(final String key, final Component component, final IModel model, final String defaultValue) { return getStringIgnoreSettings(key, component, model, null, null, defaultValue); } /** * This is similar to {@link #getString(String, Component, IModel, String)} except that the * resource settings are ignored. This allows to to code something like * *

	 * String option = getLocalizer().getStringIgnoreSettings(getId() + ".null", this, "");
	 * if (Strings.isEmpty(option))
	 * {
	 * 	option = getLocalizer().getString("null", this, CHOOSE_ONE);
	 * }
	 * 
* * @param key * The key to obtain the resource for * @param component * The component to get the resource for (optional) * @param model * The model to use for substitutions in the strings (optional) * @param locale * If != null, it'll supersede the component's locale * @param style * If != null, it'll supersede the component's style * @param defaultValue * The default value (optional) * @return The string resource */ public String getStringIgnoreSettings(final String key, final Component component, final IModel model, Locale locale, String style, final String defaultValue) { boolean addedToPage = false; if (component != null) { if ((component instanceof Page) || (null != component.findParent(Page.class))) { addedToPage = true; } if (!addedToPage && log.isWarnEnabled()) { log.warn( "Tried to retrieve a localized string for a component that has not yet been added to the page. " + "This can sometimes lead to an invalid or no localized resource returned. " + "Make sure you are not calling Component#getString() inside your Component's constructor. " + "Offending component: {} - Resource key: {}", component, key); } } String cacheKey = null; String value; // Make sure locale, style and variation have the right values String variation = (component != null ? component.getVariation() : null); if ((locale == null) && (component != null)) { locale = component.getLocale(); } if (locale == null) { locale = Session.exists() ? Session.get().getLocale() : Locale.getDefault(); } if ((style == null) && (component != null)) { style = component.getStyle(); } if (style == null) { style = Session.exists() ? Session.get().getStyle() : null; } // If this component is not yet added to page we do not want to check // cache as we can generate an invalid cache key if ((cache != null) && ((component == null) || addedToPage)) { cacheKey = getCacheKey(key, component, locale, style, variation); } // Value not found are cached as well (value = null) if ((cacheKey != null) && cache.containsKey(cacheKey)) { value = getFromCache(cacheKey); if (log.isDebugEnabled()) { log.debug("Property found in cache: '" + key + "'; Component: '" + (component != null ? component.toString(false) : null) + "'; value: '" + value + '\''); } } else { if (log.isDebugEnabled()) { log.debug("Locate property: key: '" + key + "'; Component: '" + (component != null ? component.toString(false) : null) + '\''); } // Iterate over all registered string resource loaders until the property has been found Iterator iter = getStringResourceLoaders().iterator(); value = null; while (iter.hasNext() && (value == null)) { IStringResourceLoader loader = iter.next(); value = loader.loadStringResource(component, key, locale, style, variation); } // Cache the result incl null if not found if (cacheKey != null) { putIntoCache(cacheKey, value); } if ((value == null) && log.isDebugEnabled()) { log.debug("Property not found; key: '" + key + "'; Component: '" + (component != null ? component.toString(false) : null) + '\''); } } if (value == null) { value = defaultValue; } // If a property value has been found, or a default value was given, // then replace the placeholder and we are done if (value != null) { return substitutePropertyExpressions(component, value, model); } return null; } /** * In case you want to provide your own list of string resource loaders * * @return List of string resource loaders */ protected List getStringResourceLoaders() { return Application.get().getResourceSettings().getStringResourceLoaders(); } /** * Put the value into the cache and associate it with the cache key * * @param cacheKey * @param string */ protected void putIntoCache(final String cacheKey, final String string) { if (cache == null) { return; } // ConcurrentHashMap does not allow null values if (string == null) { cache.put(cacheKey, NULL_VALUE); } else { cache.put(cacheKey, string); } } /** * Get the value associated with the key from the cache. * * @param cacheKey * @return The value of the key */ protected String getFromCache(final String cacheKey) { if (cache == null) { return null; } final String value = cache.get(cacheKey); // ConcurrentHashMap does not allow null values if (NULL_VALUE == value) { return null; } return value; } /** * Gets the cache key * * @param key * @param component * @param locale * Guaranteed to be != null * @param style * @param variation * @return The value of the key */ protected String getCacheKey(final String key, final Component component, final Locale locale, final String style, final String variation) { if (component != null) { StringBuilder buffer = new StringBuilder(200); buffer.append(key); Component cursor = component; while (cursor != null) { buffer.append('-').append(metaDatabase.id(cursor.getClass())); if (cursor instanceof Page) { break; } /* * only append component id if component is not a loop item because (a) these ids * are irrelevant when generating resource cache keys (b) they cause a lot of * redundant keys to be generated * * also if the cursor component is an auto component we append a constant string * instead of component's id because auto components have a newly generated id on * every render. */ final Component parent = cursor.getParent(); final boolean skip = parent instanceof AbstractRepeater; if (skip == false) { String cursorKey = cursor.isAuto() ? "wicket-auto" : cursor.getId(); buffer.append(':').append(cursorKey); } cursor = parent; } buffer.append('-').append(locale); buffer.append('-').append(style); buffer.append('-').append(variation); return buffer.toString(); } else { // locale is guaranteed to be != null return key + '-' + locale.toString() + '-' + style; } } /** * Helper method to handle property variable substitution in strings. * * @param component * The component requesting a model value or {@code null} * @param string * The string to substitute into * @param model * The model * @return The resulting string */ public String substitutePropertyExpressions(final Component component, final String string, final IModel model) { if ((string != null) && (model != null)) { final IConverterLocator locator; final Locale locale; if (component == null) { locator = Application.get().getConverterLocator(); if (Session.exists()) { locale = Session.get().getLocale(); } else { locale = Locale.getDefault(); } } else { locator = component; locale = component.getLocale(); } return new ConvertingPropertyVariableInterpolator(string, model.getObject(), locator, locale).toString(); } return string; } /** * By default the cache is enabled. Disabling the cache will disable it and clear the cache. * This can be handy for example in development mode. * * @param value */ public final void setEnableCache(boolean value) { if (value == false) { cache = null; } else if (cache == null) { cache = newCache(); } } /** * Create a new cache, override this method if you want a different map to store the cache keys, * for example a map that hold only the last X number of elements.. * * By default it uses the {@link ConcurrentHashMap} * * @return cache */ protected Map newCache() { return new ConcurrentHashMap<>(); } /** * Database that maps class names to an integer id. This is used to make localizer keys shorter * because sometimes they can contain a large number of class names. * * @author igor.vaynberg */ private static class ClassMetaDatabase { private final ConcurrentMap nameToId = Generics.newConcurrentHashMap(); private final AtomicLong nameCounter = new AtomicLong(); /** * Returns a unique id that represents this class' name. This can be used for compressing * class names. Notice this id should not be used across cluster nodes. * * @param clazz * @return long id of class name */ public long id(Class clazz) { final String name = clazz.getName(); Long id = nameToId.get(name); if (id == null) { id = nameCounter.incrementAndGet(); Long previousId = nameToId.putIfAbsent(name, id); if (previousId != null) { id = previousId; } } return id; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy