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    }