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

org.wisdom.i18n.InternationalizationServiceSingleton Maven / Gradle / Ivy

There is a newer version: 0.10.0
Show newest version
/*
 * #%L
 * Wisdom-Framework
 * %%
 * Copyright (C) 2013 - 2014 Wisdom Framework
 * %%
 * 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.
 * #L%
 */
package org.wisdom.i18n;

import org.apache.felix.ipojo.annotations.*;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.util.tracker.BundleTracker;
import org.osgi.util.tracker.BundleTrackerCustomizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wisdom.api.configuration.ApplicationConfiguration;
import org.wisdom.api.i18n.InternationalizationService;

import java.util.*;

/**
 * The default implementation of the internationalization service. It analyses bundles,
 * and loads resource bundles from files contained in the 'i18n' folder of the bundle. The locale is extracted from
 * the file name as follows: name_locale.properties. For example, app.properties is using the default locale,
 * while app_fr is using the French locale. Resource bundles are loaded in UTF-8.
 */
@Component
@Provides
@Instantiate
public class InternationalizationServiceSingleton implements InternationalizationService,
        BundleTrackerCustomizer> {

    private static final Logger LOGGER = LoggerFactory.getLogger(InternationalizationService.class);


    private final BundleContext context;
    private final Locale defaultLocale;

    @Requires
    ApplicationConfiguration configuration;

    /**
     * The managed extensions.
     */
    private List extensions = new ArrayList<>();
    private BundleTracker> tracker;

    public InternationalizationServiceSingleton(BundleContext context) {
        this.context = context;
        // configuration is null in unit tests (on purpose).
        if (configuration != null) {
            this.defaultLocale = Locale.forLanguageTag(configuration.getWithDefault(APPLICATION_DEFAULT_LOCALE,
                Locale.ENGLISH.toLanguageTag()));
        } else {
            this.defaultLocale = null;
        }
    }

    @Validate
    public void start() {
        tracker = new BundleTracker<>(context, Bundle.ACTIVE, this);
        tracker.open();
    }

    @Invalidate
    public void stop() {
        if (tracker != null) {
            tracker.close();
        }
        extensions.clear();
    }


    /**
     * Retrieves the set of resource bundles handled by the system.
     *
     * @return the set of resource bundle, empty if none.
     */
    @Override
    public Collection bundles() {
        Set bundles = new LinkedHashSet<>();
        for (I18nExtension extension : extensions) {
            bundles.add(extension.bundle());
        }
        return bundles;
    }

    /**
     * Retrieves the set of resource bundles handled by the system providing messages for the given locale.
     *
     * @param locale the locale
     * @return the set of resource bundle, empty if none.
     */
    @Override
    public Collection bundles(Locale locale) {
        Set bundles = new LinkedHashSet<>();
        for (I18nExtension extension : extensions) {
            if (extension.locale().equals(locale)) {
                bundles.add(extension.bundle());
            } else if (isMatchingDefaultLocale(locale, extension)) {
                bundles.add(extension.bundle());
            }
        }
        return bundles;
    }

    /**
     * Checks whether the given locale matches the application default locale and if the given extension is providing
     * the default locale.
     * @param locale the locale
     * @param extension the extension
     * @return {@literal true} if the given local eis the configured default locale and if the given extension is
     * providing messages in the default locale.
     */
    private boolean isMatchingDefaultLocale(Locale locale, I18nExtension extension) {
        return locale.equals(defaultLocale)  && extension.locale().equals(DEFAULT_LOCALE);
    }


    /**
     * Gets the message identified by `key` for the first locale from the given set of locale that provide a value.
     * The message can be parameterized using  `args`, applied to the message using  {@link java.text.MessageFormat}.
     * If the message is not provided for the given locales, the default locale is tried. If the message is still not
     * provided, {@literal null} is returned.
     *
     * @param locales the ordered set of locales
     * @param key     the key
     * @param args    the arguments (optional)
     * @return the formatted internationalized message
     */
    public String get(Locale[] locales, String key, Object... args) {
        for (Locale locale : locales) {
            I18nExtension extension = getExtension(locale, key);
            if (extension != null) {
                return extension.get(key, args);
            }
        }
        // Use default.
        I18nExtension extension = getExtension(InternationalizationService.DEFAULT_LOCALE, key);
        if (extension != null) {
            return extension.get(key, args);
        }

        return null;
    }

    /**
     * Gets the message identified by `key` for the given locale. The message can be parameterized using `args`,
     * applied to the message using  {@link java.text.MessageFormat}. If the message is not provided for the given
     * locale, the default locale is tried. If the message is still not provided, {@literal null} is returned.
     *
     * @param locale the locale
     * @param key    the key
     * @param args   the arguments (optional)
     * @return the formatted internationalized message
     */
    @Override
    public String get(Locale locale, String key, Object... args) {
        I18nExtension extension = getExtension(locale, key);
        if (extension != null) {
            return extension.get(key, args);
        }
        extension = getExtension(InternationalizationService.DEFAULT_LOCALE, key);
        if (extension != null) {
            return extension.get(key, args);
        }

        return null;
    }

    /**
     * Gets all the messages defined in the given locale AND default locale (for messages not defined in the given
     * locale). The returned map is composed pair of key:message.
     *
     * @param locale the locale
     * @return the set of defined messages.
     */
    public Map getAllMessages(Locale locale) {
        return getAllMessages(new Locale[]{locale});
    }

    /**
     * Gets all the messages defined in the given locales AND default locale (for messages not defined in the given
     * any locale). The message are added to the map only if they are not provided in the previous locale,
     * meaning that the order is important. The returned map is composed pair of key:message.
     *
     * @param locales the ordered set of locales
     * @return the set of defined messages.
     */
    @Override
    public Map getAllMessages(Locale... locales) {
        Map messages = new HashMap<>();
        List extensionForLocale;
        for (Locale locale : locales) {
            extensionForLocale = getExtension(locale);
            for (I18nExtension extension : extensionForLocale) {
                merge(messages, extension.map());
            }
        }
        // Now add the messages for the default locale
        extensionForLocale = getExtension(DEFAULT_LOCALE);
        for (I18nExtension extension : extensionForLocale) {
            merge(messages, extension.map());
        }
        return messages;
    }

    private void merge(Map map1, Map map2) {
        for (Map.Entry entry : map2.entrySet()) {
            if (!map1.containsKey(entry.getKey())) {
                map1.put(entry.getKey(), entry.getValue());
            }
        }
    }

    private List getExtension(Locale locale) {
        List list = new ArrayList<>();
        for (I18nExtension extension : extensions) {
            if (extension.locale().equals(locale)) {
                list.add(extension);
            } else if (locale.equals(defaultLocale)  && extension.locale().equals(DEFAULT_LOCALE)) {
                list.add(extension);
            }
        }
        return list;
    }

    private I18nExtension getExtension(Locale locale, String key) {
        for (I18nExtension extension : extensions) {
            if (extension.locale().equals(locale)
                    && extension.keys().contains(key)) {
                return extension;
            } else if (locale.equals(defaultLocale)  && extension.locale().equals(DEFAULT_LOCALE)) {
                return extension;
            }
        }
        return null;
    }

    /**
     * A bundle is being added to the {@code BundleTracker}.
     * 

*

* This method is called before a bundle which matched the search parameters * of the {@code BundleTracker} is added to the * {@code BundleTracker}. This method should return the object to be * tracked for the specified {@code Bundle}. The returned object is * stored in the {@code BundleTracker} and is available from the * {@link org.osgi.util.tracker.BundleTracker#getObject(org.osgi.framework.Bundle) getObject} method. * * @param bundle The {@code Bundle} being added to the * {@code BundleTracker}. * @param event The bundle event which caused this customizer method to be * called or {@code null} if there is no bundle event associated * with the call to this method. * @return The object to be tracked for the specified {@code Bundle} * object or {@code null} if the specified {@code Bundle} * object should not be tracked. */ @Override public List addingBundle(Bundle bundle, BundleEvent event) { List list = ExtenderUtils.analyze("/i18n/", bundle); if (list.isEmpty()) { return null; } LOGGER.info(list.size() + " resource bundle(s) loaded from {} ({})", bundle.getSymbolicName(), bundle.getBundleId()); extensions.addAll(list); return list; } /** * A bundle tracked by the {@code BundleTracker} has been modified. *

*

* This method is called when a bundle being tracked by the * {@code BundleTracker} has had its state modified. * * @param bundle The {@code Bundle} whose state has been modified. * @param event The bundle event which caused this customizer method to be * called or {@code null} if there is no bundle event associated * with the call to this method. * @param object The tracked object for the specified bundle. */ @Override public void modifiedBundle(Bundle bundle, BundleEvent event, List object) { // Not supported. } /** * A bundle tracked by the {@code BundleTracker} has been removed. *

*

* This method is called after a bundle is no longer being tracked by the * {@code BundleTracker}. * * @param bundle The {@code Bundle} that has been removed. * @param event The bundle event which caused this customizer method to be * called or {@code null} if there is no bundle event associated * with the call to this method. * @param list The tracked object for the specified bundle. */ @Override public void removedBundle(Bundle bundle, BundleEvent event, List list) { for (I18nExtension extension : list) { synchronized (this) { extensions.remove(extension); } } LOGGER.info("Bundle {} ({}) does not more offer the {} resource bundle(s) it provided", bundle.getSymbolicName(), bundle.getBundleId(), list.size()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy