001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.message;
018
019import java.io.IOException;
020import java.io.ObjectInputStream;
021import java.io.ObjectOutputStream;
022import java.util.Locale;
023import java.util.MissingResourceException;
024import java.util.ResourceBundle;
025
026import org.apache.logging.log4j.status.StatusLogger;
027
028/**
029 * Provides some level of compatibility with Log4j 1.x and convenience but is not the recommended way to Localize
030 * messages.
031 * <p>
032 * The recommended way to localize messages is to log a message id. Log events should then be recorded without
033 * formatting into a data store. The application that is used to read the events and display them to the user can then
034 * localize and format the messages for the end user.
035 * </p>
036 */
037public class LocalizedMessage implements Message, LoggerNameAwareMessage {
038    private static final long serialVersionUID = 3893703791567290742L;
039
040    private String baseName;
041
042    // ResourceBundle is not Serializable.
043    private transient ResourceBundle resourceBundle;
044
045    private final Locale locale;
046
047    private transient StatusLogger logger = StatusLogger.getLogger();
048
049    private String loggerName;
050    private String key;
051    private String[] stringArgs;
052    private transient Object[] argArray;
053    private String formattedMessage;
054    private transient Throwable throwable;
055
056    /**
057     * Constructor with message pattern and arguments.
058     *
059     * @param messagePattern the message pattern that to be checked for placeholders.
060     * @param arguments the argument array to be converted.
061     */
062    public LocalizedMessage(final String messagePattern, final Object[] arguments) {
063        this((ResourceBundle) null, (Locale) null, messagePattern, arguments);
064    }
065
066    public LocalizedMessage(final String baseName, final String key, final Object[] arguments) {
067        this(baseName, (Locale) null, key, arguments);
068    }
069
070    public LocalizedMessage(final ResourceBundle bundle, final String key, final Object[] arguments) {
071        this(bundle, (Locale) null, key, arguments);
072    }
073
074    public LocalizedMessage(final String baseName, final Locale locale, final String key, final Object[] arguments) {
075        this.key = key;
076        this.argArray = arguments;
077        this.throwable = null;
078        this.baseName = baseName;
079        this.resourceBundle = null;
080        this.locale = locale;
081    }
082
083    public LocalizedMessage(final ResourceBundle bundle, final Locale locale, final String key,
084            final Object[] arguments) {
085        this.key = key;
086        this.argArray = arguments;
087        this.throwable = null;
088        this.baseName = null;
089        this.resourceBundle = bundle;
090        this.locale = locale;
091    }
092
093    public LocalizedMessage(final Locale locale, final String key, final Object[] arguments) {
094        this((ResourceBundle) null, locale, key, arguments);
095    }
096
097    public LocalizedMessage(final String messagePattern, final Object arg) {
098        this((ResourceBundle) null, (Locale) null, messagePattern, new Object[] {arg});
099    }
100
101    public LocalizedMessage(final String baseName, final String key, final Object arg) {
102        this(baseName, (Locale) null, key, new Object[] {arg});
103    }
104
105    /**
106     * @since 2.8
107     */
108    public LocalizedMessage(final ResourceBundle bundle, final String key) {
109        this(bundle, (Locale) null, key, new Object[] {});
110    }
111
112    public LocalizedMessage(final ResourceBundle bundle, final String key, final Object arg) {
113        this(bundle, (Locale) null, key, new Object[] {arg});
114    }
115
116    public LocalizedMessage(final String baseName, final Locale locale, final String key, final Object arg) {
117        this(baseName, locale, key, new Object[] {arg});
118    }
119
120    public LocalizedMessage(final ResourceBundle bundle, final Locale locale, final String key, final Object arg) {
121        this(bundle, locale, key, new Object[] {arg});
122    }
123
124    public LocalizedMessage(final Locale locale, final String key, final Object arg) {
125        this((ResourceBundle) null, locale, key, new Object[] {arg});
126    }
127
128    public LocalizedMessage(final String messagePattern, final Object arg1, final Object arg2) {
129        this((ResourceBundle) null, (Locale) null, messagePattern, new Object[] {arg1, arg2});
130    }
131
132    public LocalizedMessage(final String baseName, final String key, final Object arg1, final Object arg2) {
133        this(baseName, (Locale) null, key, new Object[] {arg1, arg2});
134    }
135
136    public LocalizedMessage(final ResourceBundle bundle, final String key, final Object arg1, final Object arg2) {
137        this(bundle, (Locale) null, key, new Object[] {arg1, arg2});
138    }
139
140    public LocalizedMessage(final String baseName, final Locale locale, final String key, final Object arg1,
141            final Object arg2) {
142        this(baseName, locale, key, new Object[] {arg1, arg2});
143    }
144
145    public LocalizedMessage(final ResourceBundle bundle, final Locale locale, final String key, final Object arg1,
146            final Object arg2) {
147        this(bundle, locale, key, new Object[] {arg1, arg2});
148    }
149
150    public LocalizedMessage(final Locale locale, final String key, final Object arg1, final Object arg2) {
151        this((ResourceBundle) null, locale, key, new Object[] {arg1, arg2});
152    }
153
154    /**
155     * Set the name of the Logger.
156     * 
157     * @param name The name of the Logger.
158     */
159    @Override
160    public void setLoggerName(final String name) {
161        this.loggerName = name;
162    }
163
164    /**
165     * Returns the name of the Logger.
166     * 
167     * @return the name of the Logger.
168     */
169    @Override
170    public String getLoggerName() {
171        return this.loggerName;
172    }
173
174    /**
175     * Returns the formatted message after looking up the format in the resource bundle.
176     * 
177     * @return The formatted message String.
178     */
179    @Override
180    public String getFormattedMessage() {
181        if (formattedMessage != null) {
182            return formattedMessage;
183        }
184        ResourceBundle bundle = this.resourceBundle;
185        if (bundle == null) {
186            if (baseName != null) {
187                bundle = getResourceBundle(baseName, locale, false);
188            } else {
189                bundle = getResourceBundle(loggerName, locale, true);
190            }
191        }
192        final String myKey = getFormat();
193        final String msgPattern = (bundle == null || !bundle.containsKey(myKey)) ? myKey : bundle.getString(myKey);
194        final Object[] array = argArray == null ? stringArgs : argArray;
195        final FormattedMessage msg = new FormattedMessage(msgPattern, array);
196        formattedMessage = msg.getFormattedMessage();
197        throwable = msg.getThrowable();
198        return formattedMessage;
199    }
200
201    @Override
202    public String getFormat() {
203        return key;
204    }
205
206    @Override
207    public Object[] getParameters() {
208        if (argArray != null) {
209            return argArray;
210        }
211        return stringArgs;
212    }
213
214    @Override
215    public Throwable getThrowable() {
216        return throwable;
217    }
218
219    /**
220     * Override this to use a ResourceBundle.Control in Java 6
221     * 
222     * @param rbBaseName The base name of the resource bundle, a fully qualified class name.
223     * @param resourceBundleLocale The locale to use when formatting the message.
224     * @param loop If true the key will be treated as a package or class name and a resource bundle will be located
225     *            based on all or part of the package name. If false the key is expected to be the exact bundle id.
226     * @return The ResourceBundle.
227     */
228    protected ResourceBundle getResourceBundle(final String rbBaseName, final Locale resourceBundleLocale,
229            final boolean loop) {
230        ResourceBundle rb = null;
231
232        if (rbBaseName == null) {
233            return null;
234        }
235        try {
236            if (resourceBundleLocale != null) {
237                rb = ResourceBundle.getBundle(rbBaseName, resourceBundleLocale);
238            } else {
239                rb = ResourceBundle.getBundle(rbBaseName);
240            }
241        } catch (final MissingResourceException ex) {
242            if (!loop) {
243                logger.debug("Unable to locate ResourceBundle " + rbBaseName);
244                return null;
245            }
246        }
247
248        String substr = rbBaseName;
249        int i;
250        while (rb == null && (i = substr.lastIndexOf('.')) > 0) {
251            substr = substr.substring(0, i);
252            try {
253                if (resourceBundleLocale != null) {
254                    rb = ResourceBundle.getBundle(substr, resourceBundleLocale);
255                } else {
256                    rb = ResourceBundle.getBundle(substr);
257                }
258            } catch (final MissingResourceException ex) {
259                logger.debug("Unable to locate ResourceBundle " + substr);
260            }
261        }
262        return rb;
263    }
264
265    @Override
266    public String toString() {
267        return getFormattedMessage();
268    }
269
270    private void writeObject(final ObjectOutputStream out) throws IOException {
271        out.defaultWriteObject();
272        getFormattedMessage();
273        out.writeUTF(formattedMessage);
274        out.writeUTF(key);
275        out.writeUTF(baseName);
276        out.writeInt(argArray.length);
277        stringArgs = new String[argArray.length];
278        int i = 0;
279        for (final Object obj : argArray) {
280            stringArgs[i] = obj.toString();
281            ++i;
282        }
283        out.writeObject(stringArgs);
284    }
285
286    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
287        in.defaultReadObject();
288        formattedMessage = in.readUTF();
289        key = in.readUTF();
290        baseName = in.readUTF();
291        in.readInt();
292        stringArgs = (String[]) in.readObject();
293        logger = StatusLogger.getLogger();
294        resourceBundle = null;
295        argArray = null;
296    }
297}