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 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 * 150 * @param name The name of the Logger. 151 */ 152 @Override 153 public void setLoggerName(final String name) { 154 this.loggerName = name; 155 } 156 157 /** 158 * Returns the name of the Logger. 159 * 160 * @return the name of the Logger. 161 */ 162 @Override 163 public String getLoggerName() { 164 return this.loggerName; 165 } 166 167 /** 168 * Returns the formatted message after looking up the format in the resource bundle. 169 * 170 * @return The formatted message String. 171 */ 172 @Override 173 public String getFormattedMessage() { 174 if (formattedMessage != null) { 175 return formattedMessage; 176 } 177 ResourceBundle bundle = this.resourceBundle; 178 if (bundle == null) { 179 if (baseName != null) { 180 bundle = getResourceBundle(baseName, locale, false); 181 } else { 182 bundle = getResourceBundle(loggerName, locale, true); 183 } 184 } 185 final String myKey = getFormat(); 186 final String msgPattern = (bundle == null || !bundle.containsKey(myKey)) ? myKey : bundle.getString(myKey); 187 final Object[] array = argArray == null ? stringArgs : argArray; 188 final FormattedMessage msg = new FormattedMessage(msgPattern, array); 189 formattedMessage = msg.getFormattedMessage(); 190 throwable = msg.getThrowable(); 191 return formattedMessage; 192 } 193 194 @Override 195 public String getFormat() { 196 return key; 197 } 198 199 @Override 200 public Object[] getParameters() { 201 if (argArray != null) { 202 return argArray; 203 } 204 return stringArgs; 205 } 206 207 @Override 208 public Throwable getThrowable() { 209 return throwable; 210 } 211 212 /** 213 * Override this to use a ResourceBundle.Control in Java 6 214 * 215 * @param rbBaseName The base name of the resource bundle, a fully qualified class name. 216 * @param resourceBundleLocale The locale to use when formatting the message. 217 * @param loop If true the key will be treated as a package or class name and a resource bundle will be located 218 * based on all or part of the package name. If false the key is expected to be the exact bundle id. 219 * @return The ResourceBundle. 220 */ 221 protected ResourceBundle getResourceBundle(final String rbBaseName, final Locale resourceBundleLocale, 222 final boolean loop) { 223 ResourceBundle rb = null; 224 225 if (rbBaseName == null) { 226 return null; 227 } 228 try { 229 if (resourceBundleLocale != null) { 230 rb = ResourceBundle.getBundle(rbBaseName, resourceBundleLocale); 231 } else { 232 rb = ResourceBundle.getBundle(rbBaseName); 233 } 234 } catch (final MissingResourceException ex) { 235 if (!loop) { 236 logger.debug("Unable to locate ResourceBundle " + rbBaseName); 237 return null; 238 } 239 } 240 241 String substr = rbBaseName; 242 int i; 243 while (rb == null && (i = substr.lastIndexOf('.')) > 0) { 244 substr = substr.substring(0, i); 245 try { 246 if (resourceBundleLocale != null) { 247 rb = ResourceBundle.getBundle(substr, resourceBundleLocale); 248 } else { 249 rb = ResourceBundle.getBundle(substr); 250 } 251 } catch (final MissingResourceException ex) { 252 logger.debug("Unable to locate ResourceBundle " + substr); 253 } 254 } 255 return rb; 256 } 257 258 @Override 259 public String toString() { 260 return getFormattedMessage(); 261 } 262 263 private void writeObject(final ObjectOutputStream out) throws IOException { 264 out.defaultWriteObject(); 265 getFormattedMessage(); 266 out.writeUTF(formattedMessage); 267 out.writeUTF(key); 268 out.writeUTF(baseName); 269 out.writeInt(argArray.length); 270 stringArgs = new String[argArray.length]; 271 int i = 0; 272 for (final Object obj : argArray) { 273 stringArgs[i] = obj.toString(); 274 ++i; 275 } 276 out.writeObject(stringArgs); 277 } 278 279 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 280 in.defaultReadObject(); 281 formattedMessage = in.readUTF(); 282 key = in.readUTF(); 283 baseName = in.readUTF(); 284 final int length = in.readInt(); 285 stringArgs = (String[]) in.readObject(); 286 logger = StatusLogger.getLogger(); 287 resourceBundle = null; 288 argArray = null; 289 } 290}