    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    }