001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2015 ForgeRock AS.
026 */
027package org.forgerock.opendj.config;
028
029import java.util.HashMap;
030import java.util.Locale;
031import java.util.Map;
032import java.util.MissingResourceException;
033import java.util.ResourceBundle;
034
035import org.forgerock.i18n.LocalizableMessage;
036
037/**
038 * A class for retrieving internationalized resource properties associated with
039 * a managed object definition.
040 * <p>
041 * I18N resource properties are not available for the {@link TopCfgDefn}.
042 */
043public final class ManagedObjectDefinitionI18NResource {
044
045    /** Application-wide set of instances. */
046    private static final Map<String, ManagedObjectDefinitionI18NResource> INSTANCES = new HashMap<>();
047
048    /**
049     * Gets the internationalized resource instance which can be used to
050     * retrieve the localized descriptions for the managed objects and their
051     * associated properties and relations.
052     *
053     * @return Returns the I18N resource instance.
054     */
055    public static ManagedObjectDefinitionI18NResource getInstance() {
056        return getInstance("config.messages");
057    }
058
059    /**
060     * Gets the internationalized resource instance for the named profile.
061     *
062     * @param profile
063     *            The name of the profile.
064     * @return Returns the I18N resource instance for the named profile.
065     */
066    public static ManagedObjectDefinitionI18NResource getInstanceForProfile(String profile) {
067        return getInstance("config.profiles." + profile);
068    }
069
070    /** Get a resource instance creating it if necessary. */
071    private static synchronized ManagedObjectDefinitionI18NResource getInstance(String prefix) {
072        ManagedObjectDefinitionI18NResource instance = INSTANCES.get(prefix);
073
074        if (instance == null) {
075            instance = new ManagedObjectDefinitionI18NResource(prefix);
076            INSTANCES.put(prefix, instance);
077        }
078
079        return instance;
080    }
081
082    /** Mapping from definition to locale-based resource bundle. */
083    private final Map<AbstractManagedObjectDefinition<?, ?>, Map<Locale, ResourceBundle>> resources = new HashMap<>();
084
085    /** The resource name prefix. */
086    private final String prefix;
087
088    /** Private constructor. */
089    private ManagedObjectDefinitionI18NResource(String prefix) {
090        this.prefix = prefix;
091    }
092
093    /**
094     * Get the internationalized message associated with the specified key in
095     * the default locale.
096     *
097     * @param d
098     *            The managed object definition.
099     * @param key
100     *            The resource key.
101     * @return Returns the internationalized message associated with the
102     *         specified key in the default locale.
103     * @throws MissingResourceException
104     *             If the key was not found.
105     * @throws UnsupportedOperationException
106     *             If the provided managed object definition was the
107     *             {@link TopCfgDefn}.
108     */
109    public LocalizableMessage getMessage(AbstractManagedObjectDefinition<?, ?> d, String key) {
110        return getMessage(d, key, Locale.getDefault(), (String[]) null);
111    }
112
113    /**
114     * Get the internationalized message associated with the specified key and
115     * locale.
116     *
117     * @param d
118     *            The managed object definition.
119     * @param key
120     *            The resource key.
121     * @param locale
122     *            The locale.
123     * @return Returns the internationalized message associated with the
124     *         specified key and locale.
125     * @throws MissingResourceException
126     *             If the key was not found.
127     * @throws UnsupportedOperationException
128     *             If the provided managed object definition was the
129     *             {@link TopCfgDefn}.
130     */
131    public LocalizableMessage getMessage(AbstractManagedObjectDefinition<?, ?> d, String key, Locale locale) {
132        return getMessage(d, key, locale, (String[]) null);
133    }
134
135    /**
136     * Get the parameterized internationalized message associated with the
137     * specified key and locale.
138     *
139     * @param d
140     *            The managed object definition.
141     * @param key
142     *            The resource key.
143     * @param locale
144     *            The locale.
145     * @param args
146     *            Arguments that should be inserted into the retrieved message.
147     * @return Returns the internationalized message associated with the
148     *         specified key and locale.
149     * @throws MissingResourceException
150     *             If the key was not found.
151     * @throws UnsupportedOperationException
152     *             If the provided managed object definition was the
153     *             {@link TopCfgDefn}.
154     */
155    public LocalizableMessage getMessage(AbstractManagedObjectDefinition<?, ?> d, String key, Locale locale,
156        String... args) {
157        ResourceBundle resource = getResourceBundle(d, locale);
158
159        // TODO: use message framework directly
160        if (args != null) {
161            return LocalizableMessage.raw(resource.getString(key), (Object[]) args);
162        }
163        return LocalizableMessage.raw(resource.getString(key));
164    }
165
166    /**
167     * Get the parameterized internationalized message associated with the
168     * specified key in the default locale.
169     *
170     * @param d
171     *            The managed object definition.
172     * @param key
173     *            The resource key.
174     * @param args
175     *            Arguments that should be inserted into the retrieved message.
176     * @return Returns the internationalized message associated with the
177     *         specified key in the default locale.
178     * @throws MissingResourceException
179     *             If the key was not found.
180     * @throws UnsupportedOperationException
181     *             If the provided managed object definition was the
182     *             {@link TopCfgDefn}.
183     */
184    public LocalizableMessage getMessage(AbstractManagedObjectDefinition<?, ?> d, String key, String... args) {
185        return getMessage(d, key, Locale.getDefault(), args);
186    }
187
188    /**
189     * Forcefully removes any resource bundles associated with the provided
190     * definition and using the default locale.
191     * <p>
192     * This method is intended for internal testing only.
193     *
194     * @param d
195     *            The managed object definition.
196     */
197    synchronized void removeResourceBundle(AbstractManagedObjectDefinition<?, ?> d) {
198        removeResourceBundle(d, Locale.getDefault());
199    }
200
201    /**
202     * Forcefully removes any resource bundles associated with the provided
203     * definition and locale.
204     * <p>
205     * This method is intended for internal testing only.
206     *
207     * @param d
208     *            The managed object definition.
209     * @param locale
210     *            The locale.
211     */
212    synchronized void removeResourceBundle(AbstractManagedObjectDefinition<?, ?> d, Locale locale) {
213        // Get the locale resource mapping.
214        Map<Locale, ResourceBundle> map = resources.get(d);
215        if (map != null) {
216            map.remove(locale);
217        }
218    }
219
220    /**
221     * Forcefully adds the provided resource bundle to this I18N resource for
222     * the default locale.
223     * <p>
224     * This method is intended for internal testing only.
225     *
226     * @param d
227     *            The managed object definition.
228     * @param resoureBundle
229     *            The resource bundle to be used.
230     */
231    synchronized void setResourceBundle(AbstractManagedObjectDefinition<?, ?> d, ResourceBundle resoureBundle) {
232        setResourceBundle(d, Locale.getDefault(), resoureBundle);
233    }
234
235    /**
236     * Forcefully adds the provided resource bundle to this I18N resource.
237     * <p>
238     * This method is intended for internal testing only.
239     *
240     * @param d
241     *            The managed object definition.
242     * @param locale
243     *            The locale.
244     * @param resoureBundle
245     *            The resource bundle to be used.
246     */
247    synchronized void setResourceBundle(AbstractManagedObjectDefinition<?, ?> d, Locale locale,
248        ResourceBundle resoureBundle) {
249        // Add the resource bundle.
250        getMapping(d).put(locale, resoureBundle);
251    }
252
253    /**
254     * Retrieve the resource bundle associated with a managed object and
255     * locale, lazily loading it if necessary.
256     */
257    private synchronized ResourceBundle getResourceBundle(AbstractManagedObjectDefinition<?, ?> d, Locale locale) {
258        if (d.isTop()) {
259            throw new UnsupportedOperationException("I18n resources are not available for the "
260                + "Top configuration definition");
261        }
262
263        Map<Locale, ResourceBundle> map = getMapping(d);
264
265        // Now get the resource based on the locale, loading it if necessary.
266        ResourceBundle resourceBundle = map.get(locale);
267        if (resourceBundle == null) {
268            String baseName = prefix + "." + d.getClass().getName();
269            resourceBundle =
270                ResourceBundle.getBundle(baseName, locale, ConfigurationFramework.getInstance().getClassLoader());
271            map.put(locale, resourceBundle);
272        }
273
274        return resourceBundle;
275    }
276
277    private Map<Locale, ResourceBundle> getMapping(AbstractManagedObjectDefinition<?, ?> d) {
278        // First get the locale-resource mapping, creating it if necessary.
279        Map<Locale, ResourceBundle> map = resources.get(d);
280        if (map == null) {
281            map = new HashMap<>();
282            resources.put(d, map);
283        }
284        return map;
285    }
286}