I18nSpringConfiguration.java
package dev.orne.i18n.spring;
/*-
* #%L
* Orne I18N
* %%
* Copyright (C) 2023 Orne Developments
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
import java.util.Map;
import javax.validation.constraints.NotNull;
import org.apache.commons.lang3.Validate;
import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.type.AnnotationMetadata;
import dev.orne.i18n.context.DummyI18nResources;
import dev.orne.i18n.context.I18nContextProvider;
/**
* I18N context provider configuration for Spring. Configures a
* {@code I18nContextProvider} (an instance of
* {@code I18nSpringContextProvider} by default).
* <p>
* By default configures the provider for all {@code ClassLoader}s.
* Supports selecting a target class that will configure
* the provider for the {@code ClassLoader} of the given class and all its
* children {@code ClassLoader}, allowing different configurations for each
* application even when the library is deployed as a shared library or
* different configurations for each sub-module when the library is deployed
* at EAR level in JavaEE environments.
*
* @author <a href="https://github.com/ihernaez">(w) Iker Hernaez</a>
* @version 1.0, 2023-05
* @see I18nContextProvider
* @see I18nSpringContextProvider
* @since 0.1
*/
@API(status=Status.STABLE, since="0.1")
@Configuration
public class I18nSpringConfiguration
implements ApplicationContextAware,
MessageSourceAware,
ImportAware,
InitializingBean,
I18nContextProvider.Configurer {
/** The default I18N context provider configuration customizer. */
protected static final I18nSpringConfigurer DEFAULT_CONFIGURER = new I18nSpringConfigurer() {};
/** The Spring application context. */
private ApplicationContext applicationContext;
/** The application default {@code MessageSource}. */
private MessageSource messageSource;
/** The {@code ClassLoader} this configuration will be applied to. */
private ClassLoader target;
/**
* Creates a new instance.
*/
public I18nSpringConfiguration() {
super();
}
/**
* Returns the Spring application context.
*
* @return The Spring application context.
*/
protected ApplicationContext getApplicationContext() {
return this.applicationContext;
}
/**
* {@inheritDoc}
*/
@Override
public void setApplicationContext(
final ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
/**
* Returns the application default {@code MessageSource}.
*
* @return The application default {@code MessageSource}.
*/
protected MessageSource getMessageSource() {
return this.messageSource;
}
/**
* {@inheritDoc}
*/
@Override
public void setMessageSource(
final MessageSource messageSource) {
this.messageSource = messageSource;
}
/**
* Returns the {@code ClassLoader} this configuration will be applied to.
* <p>
* If {@code null} the configuration will be applied to current thread's
* context class loader.
*
* @return The target class loader.
*/
protected ClassLoader getTarget() {
return target;
}
/**
* Sets the {@code ClassLoader} this configuration will be applied to.
* <p>
* If {@code null} the configuration will be applied to current thread's
* context class loader.
*
* @param target The target class loader.
*/
public void setTarget(
final ClassLoader target) {
this.target = target;
}
/**
* Inherits configuration from {@code EnableI18N} annotation.
*
* @param importMetadata The import metadata
* @see EnableI18N
*/
@Override
public void setImportMetadata(
final @NotNull AnnotationMetadata importMetadata) {
Validate.notNull(importMetadata);
final Map<String, Object> annotAttrs = importMetadata.getAnnotationAttributes(
EnableI18N.class.getName());
if (annotAttrs != null) {
final Class<?> targetCls = (Class<?>) annotAttrs.get("classLoader");
if (targetCls != null && !Void.class.equals(targetCls)) {
this.target = targetCls.getClassLoader();
}
}
}
/**
* Determines the I18N context provider configuration customizer to apply.
*
* @return The I18N context provider configuration customizer.
* @throws IllegalStateException If multiple instances of {@code I18nSpringConfigurer}
* are present in current Spring context.
*/
protected @NotNull I18nSpringConfigurer determineConfigurer() {
if (this.applicationContext == null) {
return DEFAULT_CONFIGURER;
}
final Map<String, I18nSpringConfigurer> configurers = this.applicationContext.getBeansOfType(I18nSpringConfigurer.class);
if (configurers.isEmpty()) {
return DEFAULT_CONFIGURER;
}
if (configurers.size() > 1) {
throw new IllegalStateException(
"Only one I18nSpringConfigurer may exist, but multiple found: " + configurers.keySet());
}
return configurers.values().iterator().next();
}
/**
* Creates a new {@code I18nSpringContextProvider} based on configured
* properties.
* <p>
* This implementation creates an instance of
* {@code I18nSpringContextProvider}.
*
* @return The new configured {@code I18nSpringContextProvider}
*/
protected @NotNull I18nSpringContextProvider createContextProvider() {
final I18nSpringConfigurer configurer = determineConfigurer();
final I18nSpringContextProvider.Builder builder = configurer.getI18nContextProviderBuilder();
configurer.configureI18nContextProvider(builder);
if (builder.build().getDefaultI18nResources() instanceof DummyI18nResources
&& this.messageSource != null) {
builder.setDefaultI18nResources(new I18nSpringResources(this.messageSource));
}
return builder.build();
}
/**
* Sets the configured {@code I18nContextProvider} for the configured
* {@code ClassLoader}.
*/
@Override
public void afterPropertiesSet() {
final I18nContextProvider provider = createContextProvider();
if (this.target != null) {
setI18nContextProvider(this.target, provider);
} else {
setI18nContextProvider(provider);
}
}
}