copy i18n plugin source, because the jar caused compile issues

This commit is contained in:
f43nd1r 2018-08-17 02:20:46 +02:00
parent f0b99146fe
commit ae805a77da
11 changed files with 740 additions and 1 deletions

View file

@ -95,7 +95,6 @@ dependencies {
compile "com.querydsl:querydsl-apt:$queryDslVersion:jpa"
//vaadin
compile 'com.vaadin:vaadin-spring-boot-starter'
compile 'org.vaadin.spring.addons:vaadin-spring-addon-i18n:2.0.0.RELEASE'
compile 'com.vaadin:vaadin-push'
compile('org.vaadin.addon:jfreechartwrapper:4.0.0') {
exclude group: 'javax.servlet', module: 'servlet-api'

View file

@ -0,0 +1,129 @@
package org.vaadin.spring.i18n;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.MessageSource;
import org.springframework.context.support.AbstractMessageSource;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Message source that resolves the messages by querying the {@link MessageProvider}s in
* the application context. The resolved messages are cached by default. The caching can be turned off.
*
* @author Petter Holmström (petter@vaadin.com)
*/
public class CompositeMessageSource extends AbstractMessageSource implements MessageSource {
public static final String ENV_PROP_MESSAGE_FORMAT_CACHE_ENABLED = "vaadin4spring.i18n.message-format-cache.enabled";
private static final Logger LOGGER = LoggerFactory.getLogger(CompositeMessageSource.class);
private final Collection<MessageProvider> messageProviders;
private final Map<Locale, Map<String, MessageFormat>> messageFormatCache = new ConcurrentHashMap<Locale, Map<String, MessageFormat>>();
private boolean messageFormatCacheEnabled = true;
/**
* Creates a new {@code CompositeMessageSource}.
*
* @param applicationContext the application context to use when looking up
* {@link MessageProvider}s, must not be {@code null}.
*/
public CompositeMessageSource(ApplicationContext applicationContext) {
LOGGER.info("Looking up MessageProviders");
messageProviders = applicationContext.getBeansOfType(MessageProvider.class).values();
if (LOGGER.isDebugEnabled()) {
for (MessageProvider messageProvider : messageProviders) {
LOGGER.debug("Found MessageProvider [{}]", messageProvider);
}
}
LOGGER.info("Found {} MessageProvider(s)", messageProviders.size());
setMessageFormatCacheEnabled(applicationContext.getEnvironment()
.getProperty(ENV_PROP_MESSAGE_FORMAT_CACHE_ENABLED, Boolean.class, true));
}
/**
* Clears the caches of all message providers.
*
* @see MessageProvider#clearCache()
*/
public void clearMessageProviderCaches() {
for (MessageProvider messageProvider : messageProviders) {
messageProvider.clearCache();
}
}
/**
* Returns whether the resolved messages are cached or not. Default is true.
*/
public boolean isMessageFormatCacheEnabled() {
return messageFormatCacheEnabled;
}
/**
* Enables or disables caching of resolved messages. This can also be set by the
* <code>{@value #ENV_PROP_MESSAGE_FORMAT_CACHE_ENABLED}</code>
* environment property.
*/
public void setMessageFormatCacheEnabled(boolean messageFormatCacheEnabled) {
this.messageFormatCacheEnabled = messageFormatCacheEnabled;
if (messageFormatCacheEnabled) {
LOGGER.info("MessageFormat cache enabled");
} else {
LOGGER.info("MessageFormat cache disabled");
}
}
@Override
protected MessageFormat resolveCode(String s, Locale locale) {
MessageFormat messageFormat = queryCache(s, locale);
if (messageFormat == null) {
messageFormat = queryMessageProviders(s, locale);
if (messageFormat != null) {
cache(s, locale, messageFormat);
}
}
return messageFormat;
}
private MessageFormat queryCache(String s, Locale locale) {
if (messageFormatCacheEnabled) {
final Map<String, MessageFormat> cache = getMessageFormatCache(locale);
return cache.get(s);
} else {
return null;
}
}
private void cache(String s, Locale locale, MessageFormat messageFormat) {
if (messageFormatCacheEnabled) {
final Map<String, MessageFormat> cache = getMessageFormatCache(locale);
cache.put(s, messageFormat);
}
}
private MessageFormat queryMessageProviders(String s, Locale locale) {
LOGGER.debug("Querying message providers for code [{}] for locale [{}]", s, locale);
for (MessageProvider messageProvider : messageProviders) {
final MessageFormat messageFormat = messageProvider.resolveCode(s, locale);
if (messageFormat != null) {
LOGGER.debug("Code [{}] for locale [{}] found in provider [{}]", s, locale, messageProvider);
return messageFormat;
}
}
LOGGER.debug("Code [{}] for locale [{}] not found", s, locale);
return null;
}
private Map<String, MessageFormat> getMessageFormatCache(Locale locale) {
Map<String, MessageFormat> cache = messageFormatCache.get(locale);
if (cache == null) {
cache = new ConcurrentHashMap<String, MessageFormat>();
messageFormatCache.put(locale, cache);
}
return cache;
}
}

View file

@ -0,0 +1,195 @@
package org.vaadin.spring.i18n;
import com.vaadin.ui.UI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.NoSuchMessageException;
import java.util.Locale;
/**
* Helper class for resolving messages in a Vaadin UI. This is effectively a wrapper around
* {@link ApplicationContext}
* that uses the locale of the current UI to lookup messages. Use it like this:
*
* <pre>
* &#64;VaadinUI
* public class MyUI extends UI {
*
* &#64;Autowired I18N i18n;
*
* ...
*
* void init() {
* myLabel.setCaption(i18n.get("myLabel.caption"));
* }
* }
* </pre>
*
* Please note, that you also need to configure a {@link org.springframework.context.MessageSource} inside your
* application context
* that contains all the messages to resolve.
*
* @author Petter Holmström (petter@vaadin.com)
*/
public class I18N {
private final ApplicationContext applicationContext;
private final Logger logger = LoggerFactory.getLogger(getClass());
private boolean revertToDefaultBundle = true;
/**
* @param applicationContext the application context to read messages from, never {@code null}.
*/
@Autowired
public I18N(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* Returns whether {@code I18N} will try the default bundle if a message cannot be resolved for the
* current locale. By default, this is true.
*/
public boolean isRevertToDefaultBundle() {
return revertToDefaultBundle;
}
/**
* See {@link #isRevertToDefaultBundle()}.
*/
public void setRevertToDefaultBundle(boolean revertToDefaultBundle) {
this.revertToDefaultBundle = revertToDefaultBundle;
}
/**
* Tries to resolve the specified message for the current locale.
*
* @param code the code to lookup up, such as 'calculator.noRateSet', never {@code null}.
* @param arguments Array of arguments that will be filled in for params within the message (params look like "{0}",
* "{1,date}", "{2,time}"), or {@code null} if none.
* @return the resolved message, or the message code prepended with an exclamation mark if the lookup fails.
* @see ApplicationContext#getMessage(String, Object[], Locale)
* @see #getLocale()
*/
public String get(String code, Object... arguments) {
try {
return getMessage(code, null, arguments);
} catch (NoSuchMessageException ex) {
logger.warn("Tried to retrieve message with code [{}] that does not exist", code);
return "!" + code;
}
}
/**
* Tries to resolve the specified message for the given locale.
*
* @param code the code to lookup up, such as 'calculator.noRateSet', never {@code null}.
* @param locale The Locale for which it is tried to look up the code. Must not be null
* @param arguments Array of arguments that will be filled in for params within the message (params look like "{0}",
* "{1,date}", "{2,time}"), or {@code null} if none.
* @throws IllegalArgumentException if the given Locale is null
* @return the resolved message, or the message code prepended with an exclamation mark if the lookup fails.
* @see ApplicationContext#getMessage(String, Object[], Locale)
* @see Locale
*/
public String get(String code, Locale locale, Object... arguments) {
if (locale == null) {
throw new IllegalArgumentException("Locale must not be null");
}
try {
return getMessage(code, locale, arguments);
} catch (NoSuchMessageException ex) {
logger.warn("Tried to retrieve message with code [{}] that does not exist", code);
return "!" + code;
}
}
/**
* Tries to resolve the specified message for the given locale.
*
* @param code the code to lookup up, such as 'calculator.noRateSet', never {@code null}.
* @param locale The Locale for which it is tried to look up the code. Must not be null
* @param arguments Array of arguments that will be filled in for params within the message (params look like "{0}",
* "{1,date}", "{2,time}"), or {@code null} if none.
* @throws IllegalArgumentException if the given Locale is null
* @return the resolved message, or the message code prepended with an exclamation mark if the lookup fails.
* @see ApplicationContext#getMessage(String, Object[], Locale)
* @see Locale
* @deprecated Use {@link #get(String, Locale, Object...)} instead.
*/
@Deprecated
public String getWithLocale(String code, Locale locale, Object... arguments) {
return get(code, locale, arguments);
}
/**
* Tries to resolve the specified message for the current locale.
*
* @param code the code to lookup up, such as 'calculator.noRateSet', never {@code null}.
* @param defaultMessage string to return if the lookup fails, never {@code null}.
* @param arguments Array of arguments that will be filled in for params within the message (params look like "{0}",
* "{1,date}", "{2,time}"), or {@code null} if none.
* @return the resolved message, or the {@code defaultMessage} if the lookup fails.
* @see #getLocale()
*/
public String getWithDefault(String code, String defaultMessage, Object... arguments) {
try {
return getMessage(code, null, arguments);
} catch (NoSuchMessageException ex) {
return defaultMessage;
}
}
/**
* Tries to resolve the specified message for the given locale.
*
* @param code the code to lookup up, such as 'calculator.noRateSet', never {@code null}.
* @param locale The Locale for which it is tried to look up the code. Must not be null
* @param defaultMessage string to return if the lookup fails, never {@code null}.
* @param arguments Array of arguments that will be filled in for params within the message (params look like "{0}",
* "{1,date}", "{2,time}"), or {@code null} if none.
* @return the resolved message, or the {@code defaultMessage} if the lookup fails.
*/
public String getWithDefault(String code, Locale locale, String defaultMessage, Object... arguments) {
if (locale == null) {
throw new IllegalArgumentException("Locale must not be null");
}
try {
return getMessage(code, locale, arguments);
} catch (NoSuchMessageException ex) {
return defaultMessage;
}
}
private String getMessage(String code, Locale locale, Object... arguments) {
Locale actualLocale = locale == null ? getLocale() : locale;
try {
return applicationContext.getMessage(code, arguments, actualLocale);
} catch (NoSuchMessageException ex) {
if (isRevertToDefaultBundle()) {
return applicationContext.getMessage(code, arguments, null);
} else {
throw ex;
}
}
}
/**
* Gets the locale of the current Vaadin UI. If the locale can not be determinted, the default locale
* is returned instead.
*
* @return the current locale, never {@code null}.
* @see UI#getLocale()
* @see Locale#getDefault()
*/
public Locale getLocale() {
UI currentUI = UI.getCurrent();
Locale locale = currentUI == null ? null : currentUI.getLocale();
if (locale == null) {
locale = Locale.getDefault();
}
return locale;
}
}

View file

@ -0,0 +1,27 @@
package org.vaadin.spring.i18n;
import java.text.MessageFormat;
import java.util.Locale;
/**
* A {@code MessageProvider} provides messages for a {@link CompositeMessageSource}.
* There can be multiple message provider beans in the same application context.
*
* @author Petter Holmström (petter@vaadin.com)
*/
public interface MessageProvider {
/**
* Attempts to resolve the specified code for the specified locale.
*
* @param s the code of the message, must not be {@code null}.
* @param locale the locale, must not be {@code null}.
* @return a {@code MessageFormat} for the message, or {@code null} if not found.
*/
MessageFormat resolveCode(String s, Locale locale);
/**
* Clears any internal caches, forcing the message provider to resolve the codes from the original message source.
*/
void clearCache();
}

View file

@ -0,0 +1,76 @@
package org.vaadin.spring.i18n;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import javax.annotation.PreDestroy;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* This class will start up a thread that will invoke {@link CompositeMessageSource#clearMessageProviderCaches()} on
* a regular interval if enabled (by default it is disabled). This feature is intended to be used during development,
* together with a tool such as JRebel, to prevent time consuming application restarts while tweaking the translations.
* <p>
* To enable, specify the interval in seconds in the environment property
* <code>{@value #ENV_PROP_MESSAGE_PROVIDER_CACHE_CLEANUP_INTERVAL_SECONDS}</code>
* and disable the message format cache of the {@link CompositeMessageSource}.
*
* @author Petter Holmström (petter@vaadin.com)
* @see CompositeMessageSource#setMessageFormatCacheEnabled(boolean)
*/
public class MessageProviderCacheCleanupExecutor {
/**
* An environment property specifying the interval of the cache cleanups. A value of 0 (the default) will
* disable the cleanups completely.
*/
public static final String ENV_PROP_MESSAGE_PROVIDER_CACHE_CLEANUP_INTERVAL_SECONDS = "vaadin4spring.i18n.message-provider-cache.cleanup-interval-seconds";
private static final Logger LOGGER = LoggerFactory.getLogger(MessageProviderCacheCleanupExecutor.class);
private ScheduledExecutorService executorService;
private int cleanupInterval;
private ScheduledFuture<?> cleanupJob;
/**
* Creates a new {@code MessageProviderCacheCleanupExecutor} and starts up the cleanup thread if cache cleanup
* has been enabled.
*/
public MessageProviderCacheCleanupExecutor(final Environment environment,
final CompositeMessageSource compositeMessageSource) {
cleanupInterval = environment.getProperty(ENV_PROP_MESSAGE_PROVIDER_CACHE_CLEANUP_INTERVAL_SECONDS,
Integer.class, 0);
if (cleanupInterval > 0) {
if (compositeMessageSource.isMessageFormatCacheEnabled()) {
LOGGER.warn(
"The message format cache is enabled so message provider cache cleanup will not have any effect, disabling");
} else {
LOGGER.info("Cleaning up the message provider caches every {} second(s)", cleanupInterval);
executorService = Executors.newSingleThreadScheduledExecutor();
cleanupJob = executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
LOGGER.debug("Cleaning up message provider caches");
compositeMessageSource.clearMessageProviderCaches();
}
}, cleanupInterval, cleanupInterval, TimeUnit.SECONDS);
}
} else {
LOGGER.info("Message provider cache cleanup is disabled");
}
}
/**
* Shuts down the cleanup thread.
*/
@PreDestroy
public void destroy() {
if (executorService != null) {
LOGGER.info("Shutting down message provider cache cleanup thread");
cleanupJob.cancel(true);
executorService.shutdown();
}
}
}

View file

@ -0,0 +1,116 @@
package org.vaadin.spring.i18n;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
/**
* Implementation of {@link MessageProvider} that reads messages
* from {@link ResourceBundle}s with a specific base name.
*
* @author Petter Holmström (petter@vaadin.com)
*/
public class ResourceBundleMessageProvider implements MessageProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceBundleMessageProvider.class);
private final String baseName;
private final String encoding;
/**
* Creates a new {@code ResourceBundleMessageProvider} with the given base name and UTF-8 encoding.
*
* @param baseName the base name to use, must not be {@code null}.
*/
public ResourceBundleMessageProvider(String baseName) {
this(baseName, "UTF-8");
}
/**
* Creates a new {@code ResourceBundleMessageProvider} with the given base name and encoding.
*
* @param baseName the base name to use, must not be {@code null}.
* @param encoding the encoding to use when reading the resource bundle, must not be {@code null}.
*/
public ResourceBundleMessageProvider(String baseName, String encoding) {
this.baseName = baseName;
this.encoding = encoding;
}
@Override
public MessageFormat resolveCode(String s, Locale locale) {
final ResourceBundle resourceBundle = getResourceBundle(locale);
final String message = getString(resourceBundle, s);
return getMessageFormat(message, locale);
}
@Override
public void clearCache() {
ResourceBundle.clearCache(this.getClass().getClassLoader());
}
private ResourceBundle getResourceBundle(Locale locale) {
try {
return ResourceBundle.getBundle(baseName, locale, this.getClass().getClassLoader(), new MessageControl());
} catch (MissingResourceException ex) {
LOGGER.warn("No message bundle with basename [{}] found for locale [{}]", baseName, locale);
return null;
}
}
private static String getString(ResourceBundle bundle, String s) {
if (bundle == null) {
return null;
}
try {
return bundle.getString(s);
} catch (MissingResourceException ex) {
return null;
}
}
private static MessageFormat getMessageFormat(String message, Locale locale) {
if (message == null) {
return null;
}
return new MessageFormat(message, locale);
}
private class MessageControl extends ResourceBundle.Control {
@Override
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader,
boolean reload) throws IllegalAccessException, InstantiationException, IOException {
if ("java.properties".equals(format)) {
final String resourceName = toResourceName(toBundleName(baseName, locale), "properties");
final InputStream stream = loader.getResourceAsStream(resourceName);
if (stream == null) {
return null; // Not found
}
Reader reader = null;
try {
reader = new InputStreamReader(stream, encoding);
return new PropertyResourceBundle(reader);
} catch (UnsupportedEncodingException ex) {
stream.close();
throw ex;
} finally {
if (reader != null) {
reader.close();
}
}
} else {
return super.newBundle(baseName, locale, format, loader, reload);
}
}
}
}

View file

@ -0,0 +1,24 @@
package org.vaadin.spring.i18n.annotation;
import org.springframework.context.annotation.Import;
import org.vaadin.spring.i18n.config.I18NConfiguration;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Add this annotation to your application configuration to enable the {@link org.vaadin.spring.i18n.I18N} internationalization support.
*
* @author Gert-Jan Timmer (gjr.timmer@gmail.com)
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(I18NConfiguration.class)
public @interface EnableI18N {
}

View file

@ -0,0 +1,40 @@
package org.vaadin.spring.i18n.config;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.vaadin.spring.i18n.CompositeMessageSource;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.MessageProviderCacheCleanupExecutor;
/**
* Configuration class used by {@literal @}EnableVaadinI18N
*
* Spring configuration for the {@link CompositeMessageSource}. Please remember to
* define {@link org.vaadin.spring.i18n.MessageProvider} beans that can serve the message source with messages.
*
* @author Gert-Jan Timmer (gjr.timmer@gmail.com)
* @author Petter Holmström (petter@vaadin.com)
* @see I18N
* @see CompositeMessageSource
*/
@Configuration
public class I18NConfiguration {
@Bean
I18N i18n(ApplicationContext context) {
return new I18N(context);
}
@Bean
CompositeMessageSource messageSource(ApplicationContext context) {
return new CompositeMessageSource(context);
}
@Bean
MessageProviderCacheCleanupExecutor messageProviderCacheCleanupExecutor(Environment environment,
CompositeMessageSource messageSource) {
return new MessageProviderCacheCleanupExecutor(environment, messageSource);
}
}

View file

@ -0,0 +1,21 @@
package org.vaadin.spring.i18n.support;
import java.io.Serializable;
import java.util.Locale;
/**
* Interface to be implemented by all components that contain some kind of internationalized content that needs to be
* updated on the fly when the locale is changed.
*
* @see TranslatableSupport
* @author Petter Holmström (petter@vaadin.com)
*/
public interface Translatable extends Serializable {
/**
* Called when the component should update all of its translatable strings, setting locales, etc. The locale to use
*
* @param locale the new locale to use.
*/
void updateMessageStrings(Locale locale);
}

View file

@ -0,0 +1,45 @@
package org.vaadin.spring.i18n.support;
import com.vaadin.ui.Component;
import com.vaadin.ui.HasComponents;
import com.vaadin.ui.UI;
import java.util.Locale;
/**
* Implementation of {@link Translatable} intended to be used as a delegate by an owning {@link UI}.
* The {@link #updateMessageStrings(Locale)} method will traverse the entire component hierarchy of the UI and
* update the message strings of any components that implement the {@link Translatable} interface.
*
* @author Petter Holmström (petter@vaadin.com)
*/
public class TranslatableSupport implements Translatable {
private final UI ui;
/**
* Creates a new {@code TranslatableSupport}.
*
* @param ui the UI that owns the object.
*/
public TranslatableSupport(UI ui) {
this.ui = ui;
}
@Override
public void updateMessageStrings(Locale locale) {
updateMessageStrings(locale, ui);
}
private void updateMessageStrings(Locale locale, Component component) {
if (component instanceof Translatable) {
((Translatable) component).updateMessageStrings(locale);
}
if (component instanceof HasComponents) {
for (Component child : (HasComponents) component) {
updateMessageStrings(locale, child);
}
}
}
}

View file

@ -0,0 +1,67 @@
package org.vaadin.spring.i18n.support;
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.UI;
import java.util.Locale;
/**
* Base class intended to make it easier to write UIs that needs to support changing the locale on the fly.
* You are not required to extend this class to be able to use {@link Translatable} components. You can
* easily plug in the {@link TranslatableSupport} into your existing UIs.
*
* @author Petter Holmström (petter@vaadin.com)
*/
public abstract class TranslatableUI extends UI {
private final TranslatableSupport translatableSupport = new TranslatableSupport(this);
/**
* {@inheritDoc}
* <p>
* This method will also update the message strings of all {@link Translatable} components currently attached
* to the UI.
*
* @see #updateMessageStrings()
*/
@Override
public void setLocale(Locale locale) {
super.setLocale(locale);
updateMessageStrings();
}
/**
* {@inheritDoc}
* <p>
* This implementation will delegate the UI initialization to {@link #initUI(VaadinRequest)}, then update
* the message strings of all {@link Translatable} components.
*
* @see #updateMessageStrings()
*/
@Override
protected void init(VaadinRequest request) {
initUI(request);
updateMessageStrings();
}
/**
* Called by {@link #init(VaadinRequest)} to actually initialize the UI.
*
* @param request
*/
protected abstract void initUI(VaadinRequest request);
/**
* Returns the {@link TranslatableSupport} delegate owned by this UI.
*/
protected TranslatableSupport getTranslatableSupport() {
return translatableSupport;
}
/**
* Updates the message strings of all {@link Translatable} components attached to this UI.
*/
protected void updateMessageStrings() {
getTranslatableSupport().updateMessageStrings(getLocale());
}
}