1 /*
2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3  *
4  * Copyright (c) 2012-2017 Oracle and/or its affiliates. All rights reserved.
5  *
6  * The contents of this file are subject to the terms of either the GNU
7  * General Public License Version 2 only ("GPL") or the Common Development
8  * and Distribution License("CDDL") (collectively, the "License").  You
9  * may not use this file except in compliance with the License.  You can
10  * obtain a copy of the License at
11  * https://oss.oracle.com/licenses/CDDL+GPL-1.1
12  * or LICENSE.txt.  See the License for the specific
13  * language governing permissions and limitations under the License.
14  *
15  * When distributing the software, include this License Header Notice in each
16  * file and include the License file at LICENSE.txt.
17  *
18  * GPL Classpath Exception:
19  * Oracle designates this particular file as subject to the "Classpath"
20  * exception as provided by Oracle in the GPL Version 2 section of the License
21  * file that accompanied this code.
22  *
23  * Modifications:
24  * If applicable, add the following below the License Header, with the fields
25  * enclosed by brackets [] replaced by your own identifying information:
26  * "Portions Copyright [year] [name of copyright owner]"
27  *
28  * Contributor(s):
29  * If you wish your version of this file to be governed by only the CDDL or
30  * only the GPL Version 2, indicate your decision by adding "[Contributor]
31  * elects to include this software in this distribution under the [CDDL or GPL
32  * Version 2] license."  If you don't indicate a single choice of license, a
33  * recipient has the option to distribute your version of this file under
34  * either the CDDL, the GPL Version 2 or to extend the choice of license to
35  * its licensees as provided above.  However, if you add GPL Version 2 code
36  * and therefore, elected the GPL Version 2 license, then the option applies
37  * only if the new code is made subject to such option by the copyright
38  * holder.
39  */

40
41 package com.sun.mail.util;
42
43 import java.io.PrintStream;
44 import java.text.MessageFormat;
45 import java.util.logging.Level;
46 import java.util.logging.Logger;
47 import javax.mail.Session;
48
49 /**
50  * A simplified logger used by JavaMail to handle logging to a
51  * PrintStream and logging through a java.util.logging.Logger.
52  * If debug is set, messages are written to the PrintStream and
53  * prefixed by the specified prefix (which is not included in
54  * Logger messages).
55  * Messages are logged by the Logger based on the configuration
56  * of the logging system.
57  */

58
59 /*
60  * It would be so much simpler to just subclass Logger and override
61  * the log(LogRecord) method, as the javadocs suggest, but that doesn't
62  * work because Logger makes the decision about whether to log the message
63  * or not before calling the log(LogRecord) method.  Instead, we just
64  * provide the few log methods we need here.
65  */

66
67 public final class MailLogger {
68     /**
69      * For log messages.
70      */

71     private final Logger logger;
72     /**
73      * For debug output.
74      */

75     private final String prefix;
76     /**
77      * Produce debug output?
78      */

79     private final boolean debug;
80     /**
81      * Stream for debug output.
82      */

83     private final PrintStream out;
84
85     /**
86      * Construct a new MailLogger using the specified Logger name,
87      * debug prefix (e.g., "DEBUG"), debug flag, and PrintStream.
88      *
89      * @param    name    the Logger name
90      * @param    prefix    the prefix for debug output, or null for none
91      * @param    debug    if true, write to PrintStream
92      * @param    out    the PrintStream to write to
93      */

94     public MailLogger(String name, String prefix, boolean debug,
95                 PrintStream out) {
96     logger = Logger.getLogger(name);
97     this.prefix = prefix;
98     this.debug = debug;
99     this.out = out != null ? out : System.out;
100     }
101
102     /**
103      * Construct a new MailLogger using the specified classpackage
104      * name as the Logger name,
105      * debug prefix (e.g., "DEBUG"), debug flag, and PrintStream.
106      *
107      * @param    clazz    the Logger name is the package name of this class
108      * @param    prefix    the prefix for debug output, or null for none
109      * @param    debug    if true, write to PrintStream
110      * @param    out    the PrintStream to write to
111      */

112     public MailLogger(Class<?> clazz, String prefix, boolean debug,
113                 PrintStream out) {
114     String name = packageOf(clazz);
115     logger = Logger.getLogger(name);
116     this.prefix = prefix;
117     this.debug = debug;
118     this.out = out != null ? out : System.out;
119     }
120
121     /**
122      * Construct a new MailLogger using the specified classpackage
123      * name combined with the specified subname as the Logger name,
124      * debug prefix (e.g., "DEBUG"), debug flag, and PrintStream.
125      *
126      * @param    clazz    the Logger name is the package name of this class
127      * @param    subname    the Logger name relative to this Logger name
128      * @param    prefix    the prefix for debug output, or null for none
129      * @param    debug    if true, write to PrintStream
130      * @param    out    the PrintStream to write to
131      */

132     public MailLogger(Class<?> clazz, String subname, String prefix, boolean debug,
133                 PrintStream out) {
134     String name = packageOf(clazz) + "." + subname;
135     logger = Logger.getLogger(name);
136     this.prefix = prefix;
137     this.debug = debug;
138     this.out = out != null ? out : System.out;
139     }
140
141     /**
142      * Construct a new MailLogger using the specified Logger name and
143      * debug prefix (e.g., "DEBUG").  Get the debug flag and PrintStream
144      * from the Session.
145      *
146      * @param    name    the Logger name
147      * @param    prefix    the prefix for debug output, or null for none
148      * @param    session    where to get the debug flag and PrintStream
149      */

150     public MailLogger(String name, String prefix, Session session) {
151     this(name, prefix, session.getDebug(), session.getDebugOut());
152     }
153
154     /**
155      * Construct a new MailLogger using the specified classpackage
156      * name as the Logger name and the specified
157      * debug prefix (e.g., "DEBUG").  Get the debug flag and PrintStream
158      * from the Session.
159      *
160      * @param    clazz    the Logger name is the package name of this class
161      * @param    prefix    the prefix for debug output, or null for none
162      * @param    session    where to get the debug flag and PrintStream
163      */

164     public MailLogger(Class<?> clazz, String prefix, Session session) {
165     this(clazz, prefix, session.getDebug(), session.getDebugOut());
166     }
167
168     /**
169      * Create a MailLogger that uses a Logger with the specified name
170      * and prefix.  The new MailLogger uses the same debug flag and
171      * PrintStream as this MailLogger.
172      *
173      * @param    name    the Logger name
174      * @param    prefix    the prefix for debug output, or null for none
175      * @return a MailLogger for the given name and prefix.
176      */

177     public MailLogger getLogger(String name, String prefix) {
178     return new MailLogger(name, prefix, debug, out);
179     }
180
181     /**
182      * Create a MailLogger using the specified classpackage
183      * name as the Logger name and the specified prefix.
184      * The new MailLogger uses the same debug flag and
185      * PrintStream as this MailLogger.
186      *
187      * @param    clazz    the Logger name is the package name of this class
188      * @param    prefix    the prefix for debug output, or null for none
189      * @return a MailLogger for the given name and prefix.
190      */

191     public MailLogger getLogger(Class<?> clazz, String prefix) {
192     return new MailLogger(clazz, prefix, debug, out);
193     }
194
195     /**
196      * Create a MailLogger that uses a Logger whose name is composed
197      * of this MailLogger's name plus the specified sub-name, separated
198      * by a dot.  The new MailLogger uses the new prefix for debug output.
199      * This is used primarily by the protocol trace code that wants a
200      * different prefix (none).
201      *
202      * @param    subname    the Logger name relative to this Logger name
203      * @param    prefix    the prefix for debug output, or null for none
204      * @return a MailLogger for the given name and prefix.
205      */

206     public MailLogger getSubLogger(String subname, String prefix) {
207     return new MailLogger(logger.getName() + "." + subname, prefix,
208                 debug, out);
209     }
210
211     /**
212      * Create a MailLogger that uses a Logger whose name is composed
213      * of this MailLogger's name plus the specified sub-name, separated
214      * by a dot.  The new MailLogger uses the new prefix for debug output.
215      * This is used primarily by the protocol trace code that wants a
216      * different prefix (none).
217      *
218      * @param    subname    the Logger name relative to this Logger name
219      * @param    prefix    the prefix for debug output, or null for none
220      * @param    debug    the debug flag for the sub-logger
221      * @return a MailLogger for the given name and prefix.
222      */

223     public MailLogger getSubLogger(String subname, String prefix,
224                 boolean debug) {
225     return new MailLogger(logger.getName() + "." + subname, prefix,
226                 debug, out);
227     }
228
229     /**
230      * Log the message at the specified level.
231      * @param level the log level.
232      * @param msg the message.
233      */

234     public void log(Level level, String msg) {
235     ifDebugOut(msg);
236     if (logger.isLoggable(level)) {
237         final StackTraceElement frame = inferCaller();
238         logger.logp(level, frame.getClassName(), frame.getMethodName(), msg);
239     }
240     }
241
242     /**
243      * Log the message at the specified level.
244      * @param level the log level.
245      * @param msg the message.
246      * @param param1 the additional parameter.
247      */

248     public void log(Level level, String msg, Object param1) {
249     if (debug) {
250         msg = MessageFormat.format(msg, new Object[] { param1 });
251         debugOut(msg);
252     }
253     
254     if (logger.isLoggable(level)) {
255         final StackTraceElement frame = inferCaller();
256         logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, param1);
257     }
258     }
259
260     /**
261      * Log the message at the specified level.
262      * @param level the log level.
263      * @param msg the message.
264      * @param params the message parameters.
265      */

266     public void log(Level level, String msg, Object... params) {
267     if (debug) {
268         msg = MessageFormat.format(msg, params);
269         debugOut(msg);
270     }
271     
272     if (logger.isLoggable(level)) {
273         final StackTraceElement frame = inferCaller();
274         logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, params);
275     }
276     }
277
278     /**
279      * Log the message at the specified level using a format string.
280      * @param level the log level.
281      * @param msg the message format string.
282      * @param params the message parameters.
283      *
284      * @since    JavaMail 1.5.4
285      */

286     public void logf(Level level, String msg, Object... params) {
287     msg = String.format(msg, params);
288     ifDebugOut(msg);
289     logger.log(level, msg);
290     }
291
292     /**
293      * Log the message at the specified level.
294      * @param level the log level.
295      * @param msg the message.
296      * @param thrown the throwable to log.
297      */

298     public void log(Level level, String msg, Throwable thrown) {
299     if (debug) {
300         if (thrown != null) {
301         debugOut(msg + ", THROW: ");
302         thrown.printStackTrace(out);
303         } else {
304         debugOut(msg);
305         }
306     }
307  
308     if (logger.isLoggable(level)) {
309         final StackTraceElement frame = inferCaller();
310         logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, thrown);
311     }
312     }
313
314     /**
315      * Log a message at the CONFIG level.
316      * @param msg the message.
317      */

318     public void config(String msg) {
319     log(Level.CONFIG, msg);
320     }
321
322     /**
323      * Log a message at the FINE level.
324      * @param msg the message.
325      */

326     public void fine(String msg) {
327     log(Level.FINE, msg);
328     }
329
330     /**
331      * Log a message at the FINER level.
332      * @param msg the message.
333      */

334     public void finer(String msg) {
335     log(Level.FINER, msg);
336     }
337
338     /**
339      * Log a message at the FINEST level.
340      * @param msg the message.
341      */

342     public void finest(String msg) {
343     log(Level.FINEST, msg);
344     }
345
346     /**
347      * If "debug" is set, or our embedded Logger is loggable at the
348      * given level, return true.
349      * @param level the log level.
350      * @return true if loggable.
351      */

352     public boolean isLoggable(Level level) {
353     return debug || logger.isLoggable(level);
354     }
355
356     /**
357      * Common code to conditionally log debug statements.
358      * @param msg the message to log.
359      */

360     private void ifDebugOut(String msg) {
361     if (debug)
362         debugOut(msg);
363     }
364
365     /**
366      * Common formatting for debug output.
367      * @param msg the message to log.
368      */

369     private void debugOut(String msg) {
370     if (prefix != null)
371         out.println(prefix + ": " + msg);
372     else
373         out.println(msg);
374     }
375
376     /**
377      * Return the package name of the class.
378      * Sometimes there will be no Package object for the class,
379      * e.g., if the class loader hasn't created one (see Class.getPackage()).
380      * @param clazz the class source.
381      * @return the package name or an empty string.
382      */

383     private String packageOf(Class<?> clazz) {
384     Package p = clazz.getPackage();
385     if (p != null)
386         return p.getName();        // hopefully the common case
387     String cname = clazz.getName();
388     int i = cname.lastIndexOf('.');
389     if (i > 0)
390         return cname.substring(0, i);
391     // no package name, now what?
392     return "";
393     }
394
395     /**
396      * A disadvantage of not being able to use Logger directly in JavaMail
397      * code is that the "source class" information that Logger guesses will
398      * always refer to this class instead of our caller.  This method
399      * duplicates what Logger does to try to find *our* caller, so that
400      * Logger doesn't have to do it (and get the wrong answer), and because
401      * our caller is what's wanted.
402      * @return StackTraceElement that logged the message.  Treat as read-only.
403      */

404     private StackTraceElement inferCaller() {
405     // Get the stack trace.
406     StackTraceElement stack[] = (new Throwable()).getStackTrace();
407     // First, search back to a method in the Logger class.
408     int ix = 0;
409     while (ix < stack.length) {
410         StackTraceElement frame = stack[ix];
411         String cname = frame.getClassName();
412         if (isLoggerImplFrame(cname)) {
413         break;
414         }
415         ix++;
416     }
417     // Now search for the first frame before the "Logger" class.
418     while (ix < stack.length) {
419         StackTraceElement frame = stack[ix];
420         String cname = frame.getClassName();
421         if (!isLoggerImplFrame(cname)) {
422         // We've found the relevant frame.
423         return frame;
424         }
425         ix++;
426     }
427     // We haven't found a suitable frame, so just punt.  This is
428     // OK as we are only committed to making a "best effort" here.
429     return new StackTraceElement(MailLogger.class.getName(), "log",
430                              MailLogger.class.getName(), -1);
431     }
432     
433     /**
434      * Frames to ignore as part of the MailLogger to JUL bridge.
435      * @param cname the class name.
436      * @return true if the class name is part of the MailLogger bridge.
437      */

438     private boolean isLoggerImplFrame(String cname) {
439     return MailLogger.class.getName().equals(cname);
440     }
441 }
442