1 /*
2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3  *
4  * Copyright (c) 2012-2018 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      */
MailLogger(String name, String prefix, boolean debug, PrintStream out)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 class' package
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      */
MailLogger(Class<?> clazz, String prefix, boolean debug, PrintStream out)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 class' package
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      */
MailLogger(Class<?> clazz, String subname, String prefix, boolean debug, PrintStream out)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     @Deprecated
MailLogger(String name, String prefix, Session session)151     public MailLogger(String name, String prefix, Session session) {
152 	this(name, prefix, session.getDebug(), session.getDebugOut());
153     }
154 
155     /**
156      * Construct a new MailLogger using the specified class' package
157      * name as the Logger name and the specified
158      * debug prefix (e.g., "DEBUG").  Get the debug flag and PrintStream
159      * from the Session.
160      *
161      * @param	clazz	the Logger name is the package name of this class
162      * @param	prefix	the prefix for debug output, or null for none
163      * @param	session	where to get the debug flag and PrintStream
164      */
165     @Deprecated
MailLogger(Class<?> clazz, String prefix, Session session)166     public MailLogger(Class<?> clazz, String prefix, Session session) {
167 	this(clazz, prefix, session.getDebug(), session.getDebugOut());
168     }
169 
170     /**
171      * Create a MailLogger that uses a Logger with the specified name
172      * and prefix.  The new MailLogger uses the same debug flag and
173      * PrintStream as this MailLogger.
174      *
175      * @param	name	the Logger name
176      * @param	prefix	the prefix for debug output, or null for none
177      * @return a MailLogger for the given name and prefix.
178      */
getLogger(String name, String prefix)179     public MailLogger getLogger(String name, String prefix) {
180 	return new MailLogger(name, prefix, debug, out);
181     }
182 
183     /**
184      * Create a MailLogger using the specified class' package
185      * name as the Logger name and the specified prefix.
186      * The new MailLogger uses the same debug flag and
187      * PrintStream as this MailLogger.
188      *
189      * @param	clazz	the Logger name is the package name of this class
190      * @param	prefix	the prefix for debug output, or null for none
191      * @return a MailLogger for the given name and prefix.
192      */
getLogger(Class<?> clazz, String prefix)193     public MailLogger getLogger(Class<?> clazz, String prefix) {
194 	return new MailLogger(clazz, prefix, debug, out);
195     }
196 
197     /**
198      * Create a MailLogger that uses a Logger whose name is composed
199      * of this MailLogger's name plus the specified sub-name, separated
200      * by a dot.  The new MailLogger uses the new prefix for debug output.
201      * This is used primarily by the protocol trace code that wants a
202      * different prefix (none).
203      *
204      * @param	subname	the Logger name relative to this Logger name
205      * @param	prefix	the prefix for debug output, or null for none
206      * @return a MailLogger for the given name and prefix.
207      */
getSubLogger(String subname, String prefix)208     public MailLogger getSubLogger(String subname, String prefix) {
209 	return new MailLogger(logger.getName() + "." + subname, prefix,
210 				debug, out);
211     }
212 
213     /**
214      * Create a MailLogger that uses a Logger whose name is composed
215      * of this MailLogger's name plus the specified sub-name, separated
216      * by a dot.  The new MailLogger uses the new prefix for debug output.
217      * This is used primarily by the protocol trace code that wants a
218      * different prefix (none).
219      *
220      * @param	subname	the Logger name relative to this Logger name
221      * @param	prefix	the prefix for debug output, or null for none
222      * @param	debug	the debug flag for the sub-logger
223      * @return a MailLogger for the given name and prefix.
224      */
getSubLogger(String subname, String prefix, boolean debug)225     public MailLogger getSubLogger(String subname, String prefix,
226 				boolean debug) {
227 	return new MailLogger(logger.getName() + "." + subname, prefix,
228 				debug, out);
229     }
230 
231     /**
232      * Log the message at the specified level.
233      * @param level the log level.
234      * @param msg the message.
235      */
log(Level level, String msg)236     public void log(Level level, String msg) {
237 	ifDebugOut(msg);
238 	if (logger.isLoggable(level)) {
239 	    final StackTraceElement frame = inferCaller();
240 	    logger.logp(level, frame.getClassName(), frame.getMethodName(), msg);
241 	}
242     }
243 
244     /**
245      * Log the message at the specified level.
246      * @param level the log level.
247      * @param msg the message.
248      * @param param1 the additional parameter.
249      */
log(Level level, String msg, Object param1)250     public void log(Level level, String msg, Object param1) {
251 	if (debug) {
252 	    msg = MessageFormat.format(msg, new Object[] { param1 });
253 	    debugOut(msg);
254 	}
255 
256 	if (logger.isLoggable(level)) {
257 	    final StackTraceElement frame = inferCaller();
258 	    logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, param1);
259 	}
260     }
261 
262     /**
263      * Log the message at the specified level.
264      * @param level the log level.
265      * @param msg the message.
266      * @param params the message parameters.
267      */
log(Level level, String msg, Object... params)268     public void log(Level level, String msg, Object... params) {
269 	if (debug) {
270 	    msg = MessageFormat.format(msg, params);
271 	    debugOut(msg);
272 	}
273 
274 	if (logger.isLoggable(level)) {
275 	    final StackTraceElement frame = inferCaller();
276 	    logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, params);
277 	}
278     }
279 
280     /**
281      * Log the message at the specified level using a format string.
282      * @param level the log level.
283      * @param msg the message format string.
284      * @param params the message parameters.
285      *
286      * @since	JavaMail 1.5.4
287      */
logf(Level level, String msg, Object... params)288     public void logf(Level level, String msg, Object... params) {
289 	msg = String.format(msg, params);
290 	ifDebugOut(msg);
291 	logger.log(level, msg);
292     }
293 
294     /**
295      * Log the message at the specified level.
296      * @param level the log level.
297      * @param msg the message.
298      * @param thrown the throwable to log.
299      */
log(Level level, String msg, Throwable thrown)300     public void log(Level level, String msg, Throwable thrown) {
301 	if (debug) {
302 	    if (thrown != null) {
303 		debugOut(msg + ", THROW: ");
304 		thrown.printStackTrace(out);
305 	    } else {
306 		debugOut(msg);
307 	    }
308 	}
309 
310 	if (logger.isLoggable(level)) {
311 	    final StackTraceElement frame = inferCaller();
312 	    logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, thrown);
313 	}
314     }
315 
316     /**
317      * Log a message at the CONFIG level.
318      * @param msg the message.
319      */
config(String msg)320     public void config(String msg) {
321 	log(Level.CONFIG, msg);
322     }
323 
324     /**
325      * Log a message at the FINE level.
326      * @param msg the message.
327      */
fine(String msg)328     public void fine(String msg) {
329 	log(Level.FINE, msg);
330     }
331 
332     /**
333      * Log a message at the FINER level.
334      * @param msg the message.
335      */
finer(String msg)336     public void finer(String msg) {
337 	log(Level.FINER, msg);
338     }
339 
340     /**
341      * Log a message at the FINEST level.
342      * @param msg the message.
343      */
finest(String msg)344     public void finest(String msg) {
345 	log(Level.FINEST, msg);
346     }
347 
348     /**
349      * If "debug" is set, or our embedded Logger is loggable at the
350      * given level, return true.
351      * @param level the log level.
352      * @return true if loggable.
353      */
isLoggable(Level level)354     public boolean isLoggable(Level level) {
355 	return debug || logger.isLoggable(level);
356     }
357 
358     /**
359      * Common code to conditionally log debug statements.
360      * @param msg the message to log.
361      */
ifDebugOut(String msg)362     private void ifDebugOut(String msg) {
363 	if (debug)
364 	    debugOut(msg);
365     }
366 
367     /**
368      * Common formatting for debug output.
369      * @param msg the message to log.
370      */
debugOut(String msg)371     private void debugOut(String msg) {
372 	if (prefix != null)
373 	    out.println(prefix + ": " + msg);
374 	else
375 	    out.println(msg);
376     }
377 
378     /**
379      * Return the package name of the class.
380      * Sometimes there will be no Package object for the class,
381      * e.g., if the class loader hasn't created one (see Class.getPackage()).
382      * @param clazz the class source.
383      * @return the package name or an empty string.
384      */
packageOf(Class<?> clazz)385     private String packageOf(Class<?> clazz) {
386 	Package p = clazz.getPackage();
387 	if (p != null)
388 	    return p.getName();		// hopefully the common case
389 	String cname = clazz.getName();
390 	int i = cname.lastIndexOf('.');
391 	if (i > 0)
392 	    return cname.substring(0, i);
393 	// no package name, now what?
394 	return "";
395     }
396 
397     /**
398      * A disadvantage of not being able to use Logger directly in JavaMail
399      * code is that the "source class" information that Logger guesses will
400      * always refer to this class instead of our caller.  This method
401      * duplicates what Logger does to try to find *our* caller, so that
402      * Logger doesn't have to do it (and get the wrong answer), and because
403      * our caller is what's wanted.
404      * @return StackTraceElement that logged the message.  Treat as read-only.
405      */
inferCaller()406     private StackTraceElement inferCaller() {
407 	// Get the stack trace.
408 	StackTraceElement stack[] = (new Throwable()).getStackTrace();
409 	// First, search back to a method in the Logger class.
410 	int ix = 0;
411 	while (ix < stack.length) {
412 	    StackTraceElement frame = stack[ix];
413 	    String cname = frame.getClassName();
414 	    if (isLoggerImplFrame(cname)) {
415 		break;
416 	    }
417 	    ix++;
418 	}
419 	// Now search for the first frame before the "Logger" class.
420 	while (ix < stack.length) {
421 	    StackTraceElement frame = stack[ix];
422 	    String cname = frame.getClassName();
423 	    if (!isLoggerImplFrame(cname)) {
424 		// We've found the relevant frame.
425 		return frame;
426 	    }
427 	    ix++;
428 	}
429 	// We haven't found a suitable frame, so just punt.  This is
430 	// OK as we are only committed to making a "best effort" here.
431 	return new StackTraceElement(MailLogger.class.getName(), "log",
432                              MailLogger.class.getName(), -1);
433     }
434 
435     /**
436      * Frames to ignore as part of the MailLogger to JUL bridge.
437      * @param cname the class name.
438      * @return true if the class name is part of the MailLogger bridge.
439      */
isLoggerImplFrame(String cname)440     private boolean isLoggerImplFrame(String cname) {
441 	return MailLogger.class.getName().equals(cname);
442     }
443 }
444