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 */
017 package org.apache.logging.log4j.message;
018
019 import java.io.IOException;
020 import java.io.ObjectInputStream;
021 import java.io.ObjectOutputStream;
022 import java.util.Locale;
023 import java.util.MissingResourceException;
024 import java.util.ResourceBundle;
025
026 import 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 */
037 public 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 public LocalizedMessage(final ResourceBundle bundle, final String key, final Object arg) {
106 this(bundle, (Locale) null, key, new Object[] {arg});
107 }
108
109 public LocalizedMessage(final String baseName, final Locale locale, final String key, final Object arg) {
110 this(baseName, locale, key, new Object[] {arg});
111 }
112
113 public LocalizedMessage(final ResourceBundle bundle, final Locale locale, final String key, final Object arg) {
114 this(bundle, locale, key, new Object[] {arg});
115 }
116
117 public LocalizedMessage(final Locale locale, final String key, final Object arg) {
118 this((ResourceBundle) null, locale, key, new Object[] {arg});
119 }
120
121 public LocalizedMessage(final String messagePattern, final Object arg1, final Object arg2) {
122 this((ResourceBundle) null, (Locale) null, messagePattern, new Object[] {arg1, arg2});
123 }
124
125 public LocalizedMessage(final String baseName, final String key, final Object arg1, final Object arg2) {
126 this(baseName, (Locale) null, key, new Object[] {arg1, arg2});
127 }
128
129 public LocalizedMessage(final ResourceBundle bundle, final String key, final Object arg1, final Object arg2) {
130 this(bundle, (Locale) null, key, new Object[] {arg1, arg2});
131 }
132
133 public LocalizedMessage(final String baseName, final Locale locale, final String key, final Object arg1,
134 final Object arg2) {
135 this(baseName, locale, key, new Object[] {arg1, arg2});
136 }
137
138 public LocalizedMessage(final ResourceBundle bundle, final Locale locale, final String key, final Object arg1,
139 final Object arg2) {
140 this(bundle, locale, key, new Object[] {arg1, arg2});
141 }
142
143 public LocalizedMessage(final Locale locale, final String key, final Object arg1, final Object arg2) {
144 this((ResourceBundle) null, locale, key, new Object[] {arg1, arg2});
145 }
146
147 /**
148 * Set the name of the Logger.
149 * @param name The name of the Logger.
150 */
151 @Override
152 public void setLoggerName(final String name) {
153 this.loggerName = name;
154 }
155
156 /**
157 * Returns the name of the Logger.
158 * @return the name of the Logger.
159 */
160 @Override
161 public String getLoggerName() {
162 return this.loggerName;
163 }
164
165 /**
166 * Returns the formatted message after looking up the format in the resource bundle.
167 * @return The formatted message String.
168 */
169 @Override
170 public String getFormattedMessage() {
171 if (formattedMessage != null) {
172 return formattedMessage;
173 }
174 ResourceBundle bundle = this.resourceBundle;
175 if (bundle == null) {
176 if (baseName != null) {
177 bundle = getResourceBundle(baseName, locale, false);
178 } else {
179 bundle = getResourceBundle(loggerName, locale, true);
180 }
181 }
182 final String myKey = getFormat();
183 final String msgPattern = (bundle == null || !bundle.containsKey(myKey)) ?
184 myKey : bundle.getString(myKey);
185 final Object[] array = argArray == null ? stringArgs : argArray;
186 final FormattedMessage msg = new FormattedMessage(msgPattern, array);
187 formattedMessage = msg.getFormattedMessage();
188 throwable = msg.getThrowable();
189 return formattedMessage;
190 }
191
192 @Override
193 public String getFormat() {
194 return key;
195 }
196
197 @Override
198 public Object[] getParameters() {
199 if (argArray != null) {
200 return argArray;
201 }
202 return stringArgs;
203 }
204
205 @Override
206 public Throwable getThrowable() {
207 return throwable;
208 }
209
210 /**
211 * Override this to use a ResourceBundle.Control in Java 6
212 *
213 * @param rbBaseName The base name of the resource bundle, a fully qualified class name.
214 * @param resourceBundleLocale The locale to use when formatting the message.
215 * @param loop If true the key will be treated as a package or class name and a resource bundle will
216 * be located based on all or part of the package name. If false the key is expected to be the exact bundle id.
217 * @return The ResourceBundle.
218 */
219 protected ResourceBundle getResourceBundle(final String rbBaseName, final Locale resourceBundleLocale, final boolean loop) {
220 ResourceBundle rb = null;
221
222 if (rbBaseName == null) {
223 return null;
224 }
225 try {
226 if (resourceBundleLocale != null) {
227 rb = ResourceBundle.getBundle(rbBaseName, resourceBundleLocale);
228 } else {
229 rb = ResourceBundle.getBundle(rbBaseName);
230 }
231 } catch (final MissingResourceException ex) {
232 if (!loop) {
233 logger.debug("Unable to locate ResourceBundle " + rbBaseName);
234 return null;
235 }
236 }
237
238 String substr = rbBaseName;
239 int i;
240 while (rb == null && (i = substr.lastIndexOf('.')) > 0) {
241 substr = substr.substring(0, i);
242 try {
243 if (resourceBundleLocale != null) {
244 rb = ResourceBundle.getBundle(substr, resourceBundleLocale);
245 } else {
246 rb = ResourceBundle.getBundle(substr);
247 }
248 } catch (final MissingResourceException ex) {
249 logger.debug("Unable to locate ResourceBundle " + substr);
250 }
251 }
252 return rb;
253 }
254
255 private void writeObject(final ObjectOutputStream out) throws IOException {
256 out.defaultWriteObject();
257 getFormattedMessage();
258 out.writeUTF(formattedMessage);
259 out.writeUTF(key);
260 out.writeUTF(baseName);
261 out.writeInt(argArray.length);
262 stringArgs = new String[argArray.length];
263 int i = 0;
264 for (final Object obj : argArray) {
265 stringArgs[i] = obj.toString();
266 ++i;
267 }
268 out.writeObject(stringArgs);
269 }
270
271 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
272 in.defaultReadObject();
273 formattedMessage = in.readUTF();
274 key = in.readUTF();
275 baseName = in.readUTF();
276 final int length = in.readInt();
277 stringArgs = (String[]) in.readObject();
278 logger = StatusLogger.getLogger();
279 resourceBundle = null;
280 argArray = null;
281 }
282 }