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

com.cleverpine.cptranslationsutil.config.DynamicResourceBundleMessageSource Maven / Gradle / Ivy

There is a newer version: 1.2.0
Show newest version
package com.cleverpine.cptranslationsutil.config;

import com.cleverpine.cptranslationsutil.dto.ModuleProperties;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import lombok.extern.log4j.Log4j2;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.lang.Nullable;


import static com.cleverpine.cptranslationsutil.util.ErrorMessages.INVALID_LOCALE_CODE_ERROR_MESSAGE;
import static com.cleverpine.cptranslationsutil.util.ErrorMessages.PROPERTY_FILE_CREATION_ERROR_MESSAGE;
import static com.cleverpine.cptranslationsutil.util.ErrorMessages.TRANSLATION_MAP_FOR_LANGUAGE_NULL_ERROR_MESSAGE_FORMAT;

/**
 * Custom implementation of hot reload of internationalization properties.
 * Caching of properties defaults to 3600 seconds (1 hour).
 * Properties are loaded from relative to the current VM working directory.
 * Additional configuration might be needed if application is run in a web server like Tomcat.
 */
@Log4j2
public class DynamicResourceBundleMessageSource extends ReloadableResourceBundleMessageSource {
    private final String PROPERTIES_SUFFIX = ".properties";

    private final String PROPERTY_FORMAT = "%s.%s=%s%s";
    private String resourcePath = "/i18n/";
    private ResourceLoader resourceLoader;

    public DynamicResourceBundleMessageSource(
            String basename,
            String messagesPath,
            String defaultEncoding
    ) {
        this(basename, messagesPath, defaultEncoding, 3600);
    }

    public DynamicResourceBundleMessageSource(
            String basename,
            String messagesPath,
            String defaultEncoding,
            int cacheIntervalSeconds
    ) {
        log.debug("Configuring ReloadableResourceBundleMessageSource");
        this.resourcePath = messagesPath;

        log.info("Loading i18n properties from: " + this.resourcePath + basename);
        this.setBasename(this.resourcePath + basename);
        this.resourceLoader = new FileSystemResourceLoader();
        this.setResourceLoader(this.resourceLoader); //for parent ReloadableResourceBundleMessageSource
        this.setDefaultEncoding(defaultEncoding);

        if(cacheIntervalSeconds == -1){
            log.debug("Cache interval set to -1. Caching properties forever.");
        } else if(cacheIntervalSeconds > 0){
            log.debug("Caching is set to {} seconds", cacheIntervalSeconds);
        } else if(cacheIntervalSeconds == 0){
            log.warn("Caching is disabled and reload checks are run on every request! "
                    + "Do not use this option in production.");
        }
        this.setCacheSeconds(cacheIntervalSeconds);
    }

    /**
     * By documentation, the resourceLoader of ReloadableResourceBundleMessageSource
     * is overriden by the ApplicationContext. Only enable overriding it if is of type FileSystemResourceLoader.
     * @param resourceLoader the ResourceLoader object to be used by this object
     */
    @Override
    public void setResourceLoader(@Nullable ResourceLoader resourceLoader) {
        if(resourceLoader instanceof FileSystemResourceLoader){
            super.setResourceLoader(resourceLoader);
        }
    }

    public static Locale getValidLocaleOrThrowException(String language) {
        if (language == null) {
            throw new IllegalArgumentException(INVALID_LOCALE_CODE_ERROR_MESSAGE);
        }

        return new Locale(language);
    }

    protected String getResourceFileName(String basename, Locale locale) {
        return basename + "_" + locale.toString();
    }

    protected String getFullResourceFileName(String baseFileName){
        return baseFileName +  PROPERTIES_SUFFIX;
    }

    public void refreshMessages(String language, Map> moduleMap) {
        if (moduleMap == null) {
            throw new IllegalArgumentException(String.format(TRANSLATION_MAP_FOR_LANGUAGE_NULL_ERROR_MESSAGE_FORMAT, language));
        }
        Locale locale = getValidLocaleOrThrowException(language);
        String fileName = getResourceFileName(getBasenameSet().iterator().next(), locale);

        createOrOverwriteResourceForLanguage(getFullResourceFileName(fileName), moduleMap);
        PropertiesHolder propertiesHolder = createPropertiesBundle(moduleMap);
        this.refreshProperties(fileName, propertiesHolder);
    }

    private PropertiesHolder createPropertiesBundle(Map> moduleMap) {
        Map modulePropertiesMap = new HashMap<>();
        for(String module : moduleMap.keySet()){
            ModuleProperties moduleProperties = new ModuleProperties();
            moduleProperties.setTranslations(moduleMap.get(module));

            modulePropertiesMap.put(module, moduleProperties);
        }

        Properties properties = new Properties();
        properties.putAll(modulePropertiesMap);

        return new PropertiesHolder(properties, System.currentTimeMillis());
    }

    protected void createOrOverwriteResourceForLanguage(String fileName, Map> messages) {
        try {
            Resource resource = this.resourceLoader.getResource(fileName);
            File propertiesFile;

            if (!resource.exists()) {
                log.debug("Messages for new language available. Creating {}", fileName);
                propertiesFile = resource.getFile();
                propertiesFile.createNewFile();
            } else {
                propertiesFile = resource.getFile();
            }

            writeMessagesToNewPropertiesFile(messages, propertiesFile);
        } catch (IOException e) {
            throw new RuntimeException(PROPERTY_FILE_CREATION_ERROR_MESSAGE + fileName, e);
        }
    }

    private void writeMessagesToNewPropertiesFile(Map> messages, File propertiesFile) throws IOException {
        try (FileOutputStream outputStream = new FileOutputStream(propertiesFile, false)) {
            for (String module : messages.keySet()) {
                Set> moduleEntrySet = messages.get(module).entrySet();

                for (Map.Entry entry : moduleEntrySet) {
                    String line = String.format(PROPERTY_FORMAT, module, entry.getKey(), entry.getValue(), System.lineSeparator());
                    outputStream.write(line.getBytes(StandardCharsets.UTF_8));
                }
            }
        }
    }

    public String resolveCodeForMapping(String property, Locale locale){
        return this.resolveCodeWithoutArguments(property, locale);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy