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