001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.internal;
014
015import static java.util.logging.Level.*;
016import static org.apache.juneau.internal.StringUtils.*;
017
018import java.text.*;
019import java.util.*;
020import java.util.concurrent.*;
021import java.util.logging.*;
022
023import org.apache.juneau.json.*;
024import org.apache.juneau.serializer.*;
025import org.apache.juneau.transforms.*;
026
027/**
028 * Wraps and extends the {@link java.util.logging.Logger} class to provide some additional convenience methods.
029 */
030public class JuneauLogger extends java.util.logging.Logger {
031
032   private static final WriterSerializer serializer = JsonSerializer.create()
033      .pojoSwaps(
034         CalendarSwap.ISO8601DTZ.class,
035         DateSwap.ISO8601DTZ.class,
036         EnumerationSwap.class,
037         IteratorSwap.class
038      )
039      .ssq()
040      .build();
041
042   private static final ConcurrentHashMap<Class<?>,String> rbMap = new ConcurrentHashMap<>();
043
044   private final ResourceBundle rb;
045   private final java.util.logging.Logger innerLogger;
046
047   /**
048    * Get logger for specified class.
049    *
050    * @param forClass The class to create a logger for.
051    * @return A new <l>Logger</l>.
052    */
053   public static JuneauLogger getLogger(Class<?> forClass) {
054      return new JuneauLogger(java.util.logging.Logger.getLogger(forClass.getName()));
055   }
056
057   /**
058    * Get logger for specified class using the specified resource bundle name.
059    *
060    * @param forClass The class to create a logger for.
061    * @param resourceBundleName
062    *    The name of the resource bundle.
063    *    Can be any of the following formats:
064    *    <ol>
065    *       <li>An absolute path.  E.g. <js>"com/foo/nls/Messages"</js>.
066    *       <li>A path relative to the package of the class.  E.g. <js>"nls/Messages"</js>.
067    *    </ol>
068    *    Both <js>'.'</js> and <js>'/'</js> can be used as path delimiters.
069    * @return A new <l>Logger</l>.
070    */
071   public static JuneauLogger getLogger(Class<?> forClass, String resourceBundleName) {
072      return new JuneauLogger(java.util.logging.Logger.getLogger(forClass.getName(), resolveResourceBundleName(forClass, resourceBundleName)));
073   }
074
075   /**
076    * Get logger with specified name using the specified resource bundle name.
077    *
078    * @param name The name of the logger to use.
079    * @param resourceBundleName
080    *    The name of the resource bundle.
081    *    Can be any of the following formats:
082    *    <ol>
083    *       <li>An absolute path.  E.g. <js>"com/foo/nls/Messages"</js>.
084    *       <li>A path relative to the package of the class.  E.g. <js>"nls/Messages"</js>.
085    *    </ol>
086    *    Both <js>'.'</js> and <js>'/'</js> can be used as path delimiters.
087    * @return A new <l>Logger</l>.
088    */
089   public static synchronized JuneauLogger getLogger(String name, String resourceBundleName) {
090      return new JuneauLogger(java.util.logging.Logger.getLogger(name, resourceBundleName));
091   }
092
093   /**
094    * Constructor.
095    *
096    * @param innerLogger The wrapped logger.
097    */
098   protected JuneauLogger(java.util.logging.Logger innerLogger) {
099      super(innerLogger.getName(), innerLogger.getResourceBundleName());
100      this.innerLogger = innerLogger;
101      this.rb = getResourceBundle();
102   }
103
104   /**
105    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#SEVERE} level.
106    *
107    * @param msg The message to log.
108    * @param args Optional {@link MessageFormat}-style arguments.
109    */
110   public void severe(String msg, Object...args) {
111      if (isLoggable(SEVERE))
112         log(SEVERE, msg, args);
113   }
114
115   /**
116    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#WARNING} level.
117    *
118    * @param msg The message to log.
119    * @param args Optional {@link MessageFormat}-style arguments.
120    */
121   public void warning(String msg, Object...args) {
122      if (isLoggable(WARNING))
123         log(WARNING, msg, args);
124   }
125
126   /**
127    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#INFO} level.
128    *
129    * @param msg The message to log.
130    * @param args Optional {@link MessageFormat}-style arguments.
131    */
132   public void info(String msg, Object...args) {
133      if (isLoggable(INFO))
134         log(INFO, msg, args);
135   }
136
137   /**
138    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#CONFIG} level.
139    *
140    * @param msg The message to log.
141    * @param args Optional {@link MessageFormat}-style arguments.
142    */
143   public void config(String msg, Object...args) {
144      if (isLoggable(CONFIG))
145         log(CONFIG, msg, args);
146   }
147
148   /**
149    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#FINE} level.
150    *
151    * @param msg The message to log.
152    * @param args Optional {@link MessageFormat}-style arguments.
153    */
154   public void fine(String msg, Object...args) {
155      if (isLoggable(FINE))
156         log(FINE, msg, args);
157   }
158
159   /**
160    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#FINER} level.
161    *
162    * @param msg The message to log.
163    * @param args Optional {@link MessageFormat}-style arguments.
164    */
165   public void finer(String msg, Object...args) {
166      if (isLoggable(FINER))
167         log(FINER, msg, args);
168   }
169
170   /**
171    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#FINEST} level.
172    *
173    * @param msg The message to log.
174    * @param args Optional {@link MessageFormat}-style arguments.
175    */
176   public void finest(String msg, Object...args) {
177      if (isLoggable(FINEST))
178         log(FINEST, msg, args);
179   }
180
181   /**
182    * Logs an exception as {@link Level#SEVERE} level.
183    *
184    * @param t The Throwable object to log.
185    */
186   public void severe(Throwable t) {
187      if (isLoggable(SEVERE))
188         log(SEVERE, t.getLocalizedMessage(), t);
189   }
190
191   /**
192    * Logs an exception as {@link Level#WARNING} level.
193    *
194    * @param t The Throwable object to log.
195    */
196   public void warning(Throwable t) {
197      if (isLoggable(WARNING))
198         log(WARNING, t.getLocalizedMessage(), t);
199   }
200
201   /**
202    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#SEVERE} level.
203    *
204    * @param t The Throwable object associated with the event that needs to be logged.
205    * @param msg The message to log.
206    * @param args Optional {@link MessageFormat}-style arguments.
207    */
208   public void severe(Throwable t, String msg, Object...args) {
209      if (isLoggable(SEVERE))
210         log(SEVERE, getMessage(msg, args), t);
211   }
212
213   /**
214    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#WARNING} level.
215    *
216    * @param t The Throwable object associated with the event that needs to be logged.
217    * @param msg The message to log.
218    * @param args Optional {@link MessageFormat}-style arguments.
219    */
220   public void warning(Throwable t, String msg, Object...args) {
221      if (isLoggable(WARNING))
222         log(WARNING, getMessage(msg, args), t);
223   }
224
225   /**
226    * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#INFO} level.
227    *
228    * @param t The Throwable object associated with the event that needs to be logged.
229    * @param msg The message to log.
230    * @param args Optional {@link MessageFormat}-style arguments.
231    */
232   public void info(Throwable t, String msg, Object...args) {
233      if (isLoggable(INFO))
234         log(INFO, getMessage(msg, args), t);
235   }
236
237   @Override /* Logger */
238   public void log(LogRecord record) {
239      innerLogger.log(record);
240   }
241
242   @Override /* Logger */
243   public boolean isLoggable(Level level) {
244      return innerLogger.isLoggable(level);
245   }
246
247   /**
248    * Similar to {@link #log(Level, String, Object[])}, except arguments are converted to objects
249    * that are serialized using the {@link JsonSerializer#toStringObject(Object)} method.
250    *
251    * <p>
252    * This allows arbitrary POJOs to be serialized as message parameters.
253    *
254    * @param level The level of the given message.
255    * @param msg The message to log.
256    * @param args The POJO arguments.
257    */
258   public void logObjects(Level level, String msg, Object...args) {
259      if (isLoggable(level)) {
260         for (int i = 0; i < args.length; i++)
261            args[i] = serializer.toStringObject(args[i]);
262         log(level, msg, args);
263      }
264   }
265
266   private String getMessage(String msg, Object...args) {
267      if (args.length == 0)
268         return msg;
269      if (rb != null && rb.containsKey(msg))
270         msg = rb.getString(msg);
271      return format(msg, args);
272   }
273
274   private static String resolveResourceBundleName(Class<?> forClass, String path) {
275      if (isEmpty(path))
276         return null;
277      String rb = rbMap.get(forClass);
278      if (rb == null) {
279         path = path.replace('/', '.');
280         if (path.startsWith("."))
281            path = path.substring(1);
282         ClassLoader cl = forClass.getClassLoader();
283         try {
284            ResourceBundle.getBundle(path, Locale.getDefault(), cl);
285            rbMap.putIfAbsent(forClass, path);
286         } catch (MissingResourceException e) {
287            try {
288               path = forClass.getPackage().getName() + '.' + path;
289               ResourceBundle.getBundle(path, Locale.getDefault(), cl);
290               rbMap.putIfAbsent(forClass, path);
291            } catch (MissingResourceException e2) {
292               rbMap.putIfAbsent(forClass, "");
293            }
294         }
295         rb = rbMap.get(forClass);
296      }
297      return ("".equals(rb) ? null : rb);
298   }
299}