Skip to content

Package: MailHandler$TailNameFormatter

MailHandler$TailNameFormatter

nameinstructionbranchcomplexitylinemethod
MailHandler.TailNameFormatter(String)
M: 13 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
equals(Object)
M: 12 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
format(LogRecord)
M: 2 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getTail(Handler)
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
hashCode()
M: 8 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
of(String)
M: 5 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
static {...}
M: 1 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
toString()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 2009, 2024 Oracle and/or its affiliates. All rights reserved.
3: * Copyright (c) 2009, 2024 Jason Mehrens. All rights reserved.
4: *
5: * This program and the accompanying materials are made available under the
6: * terms of the Eclipse Public License v. 2.0, which is available at
7: * http://www.eclipse.org/legal/epl-2.0.
8: *
9: * This Source Code may also be made available under the following Secondary
10: * Licenses when the conditions for such availability set forth in the
11: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
12: * version 2 with the GNU Classpath Exception, which is available at
13: * https://www.gnu.org/software/classpath/license.html.
14: *
15: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
16: */
17:
18: package org.eclipse.angus.mail.util.logging;
19:
20: import jakarta.activation.DataHandler;
21: import jakarta.activation.DataSource;
22: import jakarta.activation.FileTypeMap;
23: import jakarta.activation.MimetypesFileTypeMap;
24: import jakarta.mail.Address;
25: import jakarta.mail.Authenticator;
26: import jakarta.mail.BodyPart;
27: import jakarta.mail.Message;
28: import jakarta.mail.MessageContext;
29: import jakarta.mail.MessagingException;
30: import jakarta.mail.Part;
31: import jakarta.mail.PasswordAuthentication;
32: import jakarta.mail.SendFailedException;
33: import jakarta.mail.Service;
34: import jakarta.mail.Session;
35: import jakarta.mail.Transport;
36: import jakarta.mail.internet.AddressException;
37: import jakarta.mail.internet.ContentType;
38: import jakarta.mail.internet.InternetAddress;
39: import jakarta.mail.internet.MimeBodyPart;
40: import jakarta.mail.internet.MimeMessage;
41: import jakarta.mail.internet.MimeMultipart;
42: import jakarta.mail.internet.MimePart;
43: import jakarta.mail.internet.MimeUtility;
44: import jakarta.mail.util.ByteArrayDataSource;
45: import jakarta.mail.util.StreamProvider.EncoderTypes;
46:
47: import java.io.ByteArrayInputStream;
48: import java.io.ByteArrayOutputStream;
49: import java.io.IOException;
50: import java.io.OutputStreamWriter;
51: import java.io.PrintWriter;
52: import java.io.Reader;
53: import java.io.StringReader;
54: import java.io.StringWriter;
55: import java.io.UnsupportedEncodingException;
56: import java.io.Writer;
57: import java.lang.reflect.InvocationTargetException;
58: import java.net.InetAddress;
59: import java.net.URLConnection;
60: import java.net.UnknownHostException;
61: import java.nio.charset.Charset;
62: import java.security.PrivilegedAction;
63: import java.util.Arrays;
64: import java.util.Comparator;
65: import java.util.HashMap;
66: import java.util.Locale;
67: import java.util.Map;
68: import java.util.Objects;
69: import java.util.Properties;
70: import java.util.ResourceBundle;
71: import java.util.ServiceConfigurationError;
72: import java.util.logging.ErrorManager;
73: import java.util.logging.Filter;
74: import java.util.logging.Formatter;
75: import java.util.logging.Handler;
76: import java.util.logging.Level;
77: import java.util.logging.LogRecord;
78: import java.util.logging.SimpleFormatter;
79:
80: import static org.eclipse.angus.mail.util.logging.LogManagerProperties.fromLogManager;
81:
82: /**
83: * <code>Handler</code> that formats log records as an email message.
84: *
85: * <p>
86: * This <code>Handler</code> will store a fixed number of log records used to
87: * generate a single email message. When the internal buffer reaches capacity,
88: * all log records are formatted and placed in an email which is sent to an
89: * email server. The code to manually setup this handler can be as simple as
90: * the following:
91: *
92: * <pre>
93: * Properties props = new Properties();
94: * props.put("mail.smtp.host", "my-mail-server");
95: * props.put("mail.to", "me@example.com");
96: * props.put("verify", "local");
97: * MailHandler h = new MailHandler(props);
98: * h.setLevel(Level.WARNING);
99: * </pre>
100: *
101: * <p>
102: * <b><a id="configuration">Configuration:</a></b>
103: * The LogManager should define at least one or more recipient addresses and a
104: * mail host for outgoing email. The code to setup this handler via the
105: * logging properties can be as simple as the following:
106: *
107: * <pre>
108: * #Default MailHandler settings.
109: * org.eclipse.angus.mail.util.logging.MailHandler.mail.smtp.host = my-mail-server
110: * org.eclipse.angus.mail.util.logging.MailHandler.mail.to = me@example.com
111: * org.eclipse.angus.mail.util.logging.MailHandler.level = WARNING
112: * org.eclipse.angus.mail.util.logging.MailHandler.verify = local
113: * </pre>
114: *
115: * For a custom handler, e.g. <code>com.foo.MyHandler</code>, the properties
116: * would be:
117: *
118: * <pre>
119: * #Subclass com.foo.MyHandler settings.
120: * com.foo.MyHandler.mail.smtp.host = my-mail-server
121: * com.foo.MyHandler.mail.to = me@example.com
122: * com.foo.MyHandler.level = WARNING
123: * com.foo.MyHandler.verify = local
124: * </pre>
125: *
126: * All mail <a id="top-level-properties">properties</a> documented in the
127: * <code>Jakarta Mail API</code> cascade to the LogManager by prefixing a key
128: * using the fully qualified class name of this <code>MailHandler</code> or the
129: * fully qualified derived class name dot mail property. If the prefixed
130: * property is not found, then the mail property itself is searched in the
131: * LogManager. By default each <code>MailHandler</code> is initialized using the
132: * following LogManager configuration properties where
133: * <code><handler-name></code> refers to the fully qualified class name of
134: * the handler. If properties are not defined, or contain invalid values, then
135: * the specified default values are used.
136: *
137: * <ul>
138: * <li><handler-name>.attachment.filters a comma
139: * separated list of <code>Filter</code> class names used to create each
140: * attachment. The literal <code>null</code> is reserved for attachments that
141: * do not require filtering. (defaults to the
142: * {@linkplain java.util.logging.Handler#getFilter() body} filter)
143: *
144: * <li><handler-name>.attachment.formatters a comma
145: * separated list of <code>Formatter</code> class names used to create each
146: * attachment. (default is no attachments)
147: *
148: * <li><handler-name>.attachment.names a comma separated
149: * list of names or <code>Formatter</code> class names of each attachment. All
150: * control characters are removed from the attachment names.
151: * (default is {@linkplain java.util.logging.Formatter#toString() toString}
152: * of the attachment formatter)
153: *
154: * <li><handler-name>.authenticator name of an
155: * {@linkplain jakarta.mail.Authenticator} class used to provide login credentials
156: * to the email server or string literal that is the password used with the
157: * {@linkplain Authenticator#getDefaultUserName() default} user name.
158: * (default is <code>null</code>)
159: *
160: * <li><handler-name>.capacity the max number of
161: * <code>LogRecord</code> objects include in each email message.
162: * (defaults to <code>1000</code>)
163: *
164: * <li><handler-name>.comparator name of a
165: * {@linkplain java.util.Comparator} class used to sort the published
166: * <code>LogRecord</code> objects prior to all formatting.
167: * (defaults to <code>null</code> meaning records are unsorted).
168: *
169: * <li><handler-name>.comparator.reverse a boolean
170: * <code>true</code> to reverse the order of the specified comparator or
171: * <code>false</code> to retain the original order.
172: * (defaults to <code>false</code>)
173: *
174: * <li><handler-name>.enabled a boolean
175: * <code>true</code> to allow this handler to accept records or
176: * <code>false</code> to turn off this handler.
177: * (defaults to <code>true</code>)
178: *
179: * <li><handler-name>.encoding the name of the Java
180: * {@linkplain java.nio.charset.Charset#name() character set} to use for the
181: * email message. (defaults to <code>null</code>, the
182: * {@linkplain jakarta.mail.internet.MimeUtility#getDefaultJavaCharset() default}
183: * platform encoding).
184: *
185: * <li><handler-name>.errorManager name of an
186: * <code>ErrorManager</code> class used to handle any configuration or mail
187: * transport problems. (defaults to <code>java.util.logging.ErrorManager</code>)
188: *
189: * <li><handler-name>.filter name of a <code>Filter</code>
190: * class used for the body of the message. (defaults to <code>null</code>,
191: * allow all records)
192: *
193: * <li><handler-name>.formatter name of a
194: * <code>Formatter</code> class used to format the body of this message.
195: * (defaults to <code>java.util.logging.SimpleFormatter</code>)
196: *
197: * <li><handler-name>.level specifies the default level
198: * for this <code>Handler</code> (defaults to <code>Level.WARNING</code>).
199: *
200: * <li><handler-name>.mail.bcc a comma separated list of
201: * addresses which will be blind carbon copied. Typically, this is set to the
202: * recipients that may need to be privately notified of a log message or
203: * notified that a log message was sent to a third party such as a support team.
204: * The empty string can be used to specify no blind carbon copied address.
205: * (defaults to <code>null</code>, none)
206: *
207: * <li><handler-name>.mail.cc a comma separated list of
208: * addresses which will be carbon copied. Typically, this is set to the
209: * recipients that may need to be notified of a log message but, are not
210: * required to provide direct support. The empty string can be used to specify
211: * no carbon copied address. (defaults to <code>null</code>, none)
212: *
213: * <li><handler-name>.mail.from a comma separated list of
214: * addresses which will be from addresses. Typically, this is set to the email
215: * address identifying the user running the application. The empty string can
216: * be used to override the default behavior and specify no from address.
217: * (defaults to the {@linkplain jakarta.mail.Message#setFrom() local address})
218: *
219: * <li><handler-name>.mail.host the host name or IP
220: * address of the email server. (defaults to <code>null</code>, use
221: * {@linkplain Transport#protocolConnect default}
222: * <code>Java Mail</code> behavior)
223: *
224: * <li><handler-name>.mail.reply.to a comma separated
225: * list of addresses which will be reply-to addresses. Typically, this is set
226: * to the recipients that provide support for the application itself. The empty
227: * string can be used to specify no reply-to address.
228: * (defaults to <code>null</code>, none)
229: *
230: * <li><handler-name>.mail.to a comma separated list of
231: * addresses which will be send-to addresses. Typically, this is set to the
232: * recipients that provide support for the application, system, and/or
233: * supporting infrastructure. The empty string can be used to specify no
234: * send-to address which overrides the default behavior. (defaults to
235: * {@linkplain jakarta.mail.internet.InternetAddress#getLocalAddress
236: * local address}.)
237: *
238: * <li><handler-name>.mail.sender a single address
239: * identifying sender of the email; never equal to the from address. Typically,
240: * this is set to the email address identifying the application itself. The
241: * empty string can be used to specify no sender address.
242: * (defaults to <code>null</code>, none)
243: *
244: * <li><handler-name>.mailEntries specifies the mail session properties
245: * for this <code>Handler</code>. The format for the value is described in
246: * {@linkplain #setMailEntries(java.lang.String) setMailEntries} method.
247: * This property eagerly loads the assigned mail properties where as the
248: * <a href="#top-level-properties">top level mail properties</a> are lazily
249: * loaded. Prefer using this property when <a href="#verify">verification</a>
250: * is off or when verification does not force the provider to load required
251: * mail properties. (defaults to <code>null</code>).
252: *
253: * <li><handler-name>.subject the name of a
254: * <code>Formatter</code> class or string literal used to create the subject
255: * line. The empty string can be used to specify no subject. All control
256: * characters are removed from the subject line. (defaults to {@linkplain
257: * CollectorFormatter CollectorFormatter}.)
258: *
259: * <li><handler-name>.pushFilter the name of a
260: * <code>Filter</code> class used to trigger an early push.
261: * (defaults to <code>null</code>, no early push)
262: *
263: * <li><handler-name>.pushLevel the level which will
264: * trigger an early push. (defaults to <code>Level.OFF</code>, only push when
265: * full)
266: *
267: * <li><handler-name>.verify <a id="verify">used</a> to
268: * verify the <code>Handler</code> configuration prior to a push.
269: * <ul>
270: * <li>If the value is not set, equal to an empty string, or equal to the
271: * literal <code>null</code> then no settings are verified prior to a push.
272: * <li>If set to a value of <code>limited</code> then the
273: * <code>Handler</code> will verify minimal local machine settings.
274: * <li>If set to a value of <code>local</code> the <code>Handler</code>
275: * will verify all of settings of the local machine.
276: * <li>If set to a value of <code>resolve</code>, the <code>Handler</code>
277: * will verify all local settings and try to resolve the remote host name
278: * with the domain name server.
279: * <li>If set to a value of <code>login</code>, the <code>Handler</code>
280: * will verify all local settings and try to establish a connection with
281: * the email server.
282: * <li>If set to a value of <code>remote</code>, the <code>Handler</code>
283: * will verify all local settings, try to establish a connection with the
284: * email server, and try to verify the envelope of the email message.
285: * </ul>
286: * If this <code>Handler</code> is only implicitly closed by the
287: * <code>LogManager</code>, then verification should be turned on.
288: * (defaults to <code>null</code>, no verify).
289: * </ul>
290: *
291: * <p>
292: * <b>Normalization:</b>
293: * The error manager, filters, and formatters when loaded from the LogManager
294: * are converted into canonical form inside the MailHandler. The pool of
295: * interned values is limited to each MailHandler object such that no two
296: * MailHandler objects created by the LogManager will be created sharing
297: * identical error managers, filters, or formatters. If a filter or formatter
298: * should <b>not</b> be interned then it is recommended to retain the identity
299: * equals and identity hashCode methods as the implementation. For a filter or
300: * formatter to be interned the class must implement the
301: * {@linkplain java.lang.Object#equals(java.lang.Object) equals}
302: * and {@linkplain java.lang.Object#hashCode() hashCode} methods.
303: * The recommended code to use for stateless filters and formatters is:
304: * <pre>
305: * public boolean equals(Object obj) {
306: * return obj == null ? false : obj.getClass() == getClass();
307: * }
308: *
309: * public int hashCode() {
310: * return 31 * getClass().hashCode();
311: * }
312: * </pre>
313: *
314: * <p>
315: * <b>Sorting:</b>
316: * All <code>LogRecord</code> objects are ordered prior to formatting if this
317: * <code>Handler</code> has a non null comparator. Developers might be
318: * interested in sorting the formatted email by thread id, time, and sequence
319: * properties of a <code>LogRecord</code>. Where as system administrators might
320: * be interested in sorting the formatted email by thrown, level, time, and
321: * sequence properties of a <code>LogRecord</code>. If comparator for this
322: * handler is <code>null</code> then the order is unspecified.
323: *
324: * <p>
325: * <b>Formatting:</b>
326: * The main message body is formatted using the <code>Formatter</code> returned
327: * by <code>getFormatter()</code>. Only records that pass the filter returned
328: * by <code>getFilter()</code> will be included in the message body. The
329: * subject <code>Formatter</code> will see all <code>LogRecord</code> objects
330: * that were published regardless of the current <code>Filter</code>. The MIME
331: * type of the message body can be
332: * {@linkplain FileTypeMap#setDefaultFileTypeMap overridden}
333: * by adding a MIME {@linkplain MimetypesFileTypeMap entry} using the simple
334: * class name of the body formatter as the file extension. The MIME type of the
335: * attachments can be overridden by changing the attachment file name extension
336: * or by editing the default MIME entry for a specific file name extension.
337: *
338: * <p>
339: * <b>Attachments:</b>
340: * This <code>Handler</code> allows multiple attachments per each email message.
341: * The presence of an attachment formatter will change the content type of the
342: * email message to a multi-part message. The attachment order maps directly to
343: * the array index order in this <code>Handler</code> with zero index being the
344: * first attachment. The number of attachment formatters controls the number of
345: * attachments per email and the content type of each attachment. The
346: * attachment filters determine if a <code>LogRecord</code> will be included in
347: * an attachment. If an attachment filter is <code>null</code> then all records
348: * are included for that attachment. Attachments without content will be
349: * omitted from email message. The attachment name formatters create the file
350: * name for an attachment. Custom attachment name formatters can be used to
351: * generate an attachment name based on the contents of the attachment.
352: *
353: * <p>
354: * <b>Push Level and Push Filter:</b>
355: * The push method, push level, and optional push filter can be used to
356: * conditionally trigger a push at or prior to full capacity. When a push
357: * occurs, the current buffer is formatted into an email and is sent to the
358: * email server. If the push method, push level, or push filter trigger a push
359: * then the outgoing email is flagged as high importance with urgent priority.
360: *
361: * <p>
362: * <b>Buffering:</b>
363: * Log records that are published are stored in an internal buffer. When this
364: * buffer reaches capacity the existing records are formatted and sent in an
365: * email. Any published records can be sent before reaching capacity by
366: * explictly calling the <code>flush</code>, <code>push</code>, or
367: * <code>close</code> methods. If a circular buffer is required then this
368: * handler can be wrapped with a {@linkplain java.util.logging.MemoryHandler}
369: * typically with an equivalent capacity, level, and push level.
370: *
371: * <p>
372: * <b>Error Handling:</b>
373: * If the transport of an email message fails, the email is converted to
374: * a {@linkplain jakarta.mail.internet.MimeMessage#writeTo raw}
375: * {@linkplain java.io.ByteArrayOutputStream#toString(java.lang.String) string}
376: * and is then passed as the <code>msg</code> parameter to
377: * {@linkplain Handler#reportError reportError} along with the exception
378: * describing the cause of the failure. This allows custom error managers to
379: * store, {@linkplain jakarta.mail.internet.MimeMessage#MimeMessage(
380: *jakarta.mail.Session, java.io.InputStream) reconstruct}, and resend the
381: * original MimeMessage. The message parameter string is <b>not</b> a raw email
382: * if it starts with value returned from <code>Level.SEVERE.getName()</code>.
383: * Custom error managers can use the following test to determine if the
384: * <code>msg</code> parameter from this handler is a raw email:
385: *
386: * <pre>
387: * public void error(String msg, Exception ex, int code) {
388: * if (msg == null || msg.length() == 0 || msg.startsWith(Level.SEVERE.getName())) {
389: * super.error(msg, ex, code);
390: * } else {
391: * //The 'msg' parameter is a raw email.
392: * }
393: * }
394: * </pre>
395: *
396: * @author Jason Mehrens
397: * @since JavaMail 1.4.3
398: */
399: public class MailHandler extends Handler {
400: /**
401: * Use the emptyFilterArray method.
402: */
403: private static final Filter[] EMPTY_FILTERS = new Filter[0];
404: /**
405: * Use the emptyFormatterArray method.
406: */
407: private static final Formatter[] EMPTY_FORMATTERS = new Formatter[0];
408: /**
409: * Min byte size for header data. Used for initial arrays sizing.
410: */
411: private static final int MIN_HEADER_SIZE = 1024;
412: /**
413: * Default capacity for the log record storage.
414: */
415: private static final int DEFAULT_CAPACITY = 1000;
416: /**
417: * Cache the off value.
418: */
419: private static final int offValue = Level.OFF.intValue();
420: /**
421: * The action to set the context class loader for use with the Jakarta Mail API.
422: * Load and pin this before it is loaded in the close method. The field is
423: * declared as java.security.PrivilegedAction so
424: * WebappClassLoader.clearReferencesStaticFinal() method will ignore this
425: * field.
426: */
427: private static final PrivilegedAction<Object> MAILHANDLER_LOADER
428: = new GetAndSetContext(MailHandler.class);
429: /**
430: * A thread local mutex used to prevent logging loops. This code has to be
431: * prepared to deal with unexpected null values since the
432: * WebappClassLoader.clearReferencesThreadLocals() and
433: * InnocuousThread.eraseThreadLocals() can remove thread local values.
434: * The MUTEX has 5 states:
435: * 1. A null value meaning default state of not publishing.
436: * 2. MUTEX_PUBLISH on first entry of a push or publish.
437: * 3. The index of the first filter to accept a log record.
438: * 4. MUTEX_REPORT when cycle of records is detected.
439: * 5. MUTEXT_LINKAGE when a linkage error is reported.
440: */
441: private static final ThreadLocal<Integer> MUTEX = new ThreadLocal<>();
442: /**
443: * The marker object used to report a publishing state.
444: * This must be less than the body filter index (-1).
445: */
446: private static final Integer MUTEX_PUBLISH = -2;
447: /**
448: * The used for the error reporting state.
449: * This must be less than the PUBLISH state.
450: */
451: private static final Integer MUTEX_REPORT = -4;
452: /**
453: * The used for service configuration error reporting.
454: * This must be less than the REPORT state and less than linkage.
455: */
456: private static final Integer MUTEX_SERVICE = -8;
457: /**
458: * The used for linkage error reporting.
459: * This must be less than the REPORT state.
460: */
461: private static final Integer MUTEX_LINKAGE = -16;
462: /**
463: * Used to turn off security checks.
464: */
465: private volatile boolean sealed;
466: /**
467: * Determines if we are inside of a push.
468: * Makes the handler properties read-only during a push.
469: */
470: private boolean isWriting;
471: /**
472: * Holds all of the email server properties.
473: */
474: private Properties mailProps = new Properties();
475: /**
476: * Holds the authenticator required to login to the email server.
477: */
478: private Authenticator auth;
479: /**
480: * Holds the session object used to generate emails.
481: * Sessions can be shared by multiple threads.
482: * See JDK-6228391 and K 6278.
483: */
484: private Session session;
485: /**
486: * A mapping of log record to matching filter index. Negative one is used
487: * to track the body filter. Zero and greater is used to track the
488: * attachment parts. All indexes less than or equal to the matched value
489: * have already seen the given log record.
490: */
491: private int[] matched;
492: /**
493: * Holds all of the log records that will be used to create the email.
494: */
495: private LogRecord[] data;
496: /**
497: * The number of log records in the buffer.
498: */
499: private int size;
500: /**
501: * The maximum number of log records to format per email.
502: * Used to roughly bound the size of an email.
503: * Every time the capacity is reached, the handler will push.
504: * Capacity is zero while the handler is being constructed.
505: * The capacity will be negative if this handler is closed.
506: * Negative values are used to ensure all records are pushed.
507: */
508: private int capacity;
509: /**
510: * The level recorded at the time the handler was disabled.
511: * Null means enabled and non-null is disabled.
512: */
513: private Level disabledLevel;
514: /**
515: * Used to order all log records prior to formatting. The main email body
516: * and all attachments use the order determined by this comparator. If no
517: * comparator is present the log records will be in no specified order.
518: */
519: private Comparator<? super LogRecord> comparator;
520: /**
521: * Holds the formatter used to create the subject line of the email.
522: * A subject formatter is not required for the email message.
523: * All published records pass through the subject formatter.
524: */
525: private Formatter subjectFormatter;
526: /**
527: * Holds the push level for this handler.
528: * This is only required if an email must be sent prior to shutdown
529: * or before the buffer is full.
530: */
531: private Level pushLevel = Level.OFF;
532: /**
533: * Holds the push filter for trigger conditions requiring an early push.
534: * Only gets called if the given log record is greater than or equal
535: * to the push level and the push level is not Level.OFF.
536: */
537: private Filter pushFilter;
538: /**
539: * Holds the entry and body filter for this handler.
540: * There is no way to un-seal the super handler.
541: */
542: private volatile Filter filter;
543: /**
544: * Holds the level for this handler. Default value must be OFF.
545: * There is no way to un-seal the super handler.
546: * @see #init(java.util.Properties)
547: */
548: private volatile Level logLevel = Level.OFF;
549: /**
550: * Holds the filters for each attachment. Filters are optional for
551: * each attachment. This is declared volatile because this is treated as
552: * copy-on-write. The VO_VOLATILE_REFERENCE_TO_ARRAY warning is a false
553: * positive.
554: */
555: @SuppressWarnings("VolatileArrayField")
556: private volatile Filter[] attachmentFilters = emptyFilterArray();
557: /**
558: * Holds the encoding name for this handler.
559: * There is no way to un-seal the super handler.
560: */
561: private String encoding;
562: /**
563: * Holds the body formatter for this handler.
564: * There is no way to un-seal the super handler.
565: */
566: private Formatter formatter;
567: /**
568: * Holds the formatters that create the content for each attachment.
569: * Each formatter maps directly to an attachment. The formatters
570: * getHead, format, and getTail methods are only called if one or more
571: * log records pass through the attachment filters.
572: */
573: private Formatter[] attachmentFormatters = emptyFormatterArray();
574: /**
575: * Holds the formatters that create the file name for each attachment.
576: * Each formatter must produce a non null and non empty name.
577: * The final file name will be the concatenation of one getHead call, plus
578: * all of the format calls, plus one getTail call.
579: */
580: private Formatter[] attachmentNames = emptyFormatterArray();
581: /**
582: * Used to override the content type for the body and set the content type
583: * for each attachment.
584: */
585: private FileTypeMap contentTypes;
586: /**
587: * Holds the error manager for this handler.
588: * There is no way to un-seal the super handler.
589: */
590: @SuppressWarnings("this-escape")
591: private volatile ErrorManager errorManager = defaultErrorManager();
592:
593: /**
594: * Creates a <code>MailHandler</code> that is configured by the
595: * <code>LogManager</code> configuration properties.
596: *
597: * @throws SecurityException if a security manager exists and the
598: * caller does not have <code>LoggingPermission("control")</code>.
599: */
600: @SuppressWarnings("this-escape")
601: public MailHandler() {
602: init((Properties) null);
603: }
604:
605: /**
606: * Creates a <code>MailHandler</code> that is configured by the
607: * <code>LogManager</code> configuration properties but overrides the
608: * <code>LogManager</code> capacity with the given capacity.
609: *
610: * @param capacity of the internal buffer. If less than one the default of
611: * 1000 is used.
612: * @throws SecurityException if a security manager exists and the
613: * caller does not have <code>LoggingPermission("control")</code>.
614: */
615: @SuppressWarnings("this-escape")
616: public MailHandler(final int capacity) {
617: init((Properties) null);
618: setCapacity0(capacity);
619: }
620:
621: /**
622: * Creates a mail handler with the given mail properties.
623: * The key/value pairs are defined in the <code>Java Mail API</code>
624: * documentation. This <code>Handler</code> will also search the
625: * <code>LogManager</code> for defaults if needed.
626: *
627: * @param props a properties object or null. A null value will supply the
628: * <code>mailEntries</code> from the <code>LogManager</code>.
629: * @throws SecurityException if a security manager exists and the
630: * caller does not have <code>LoggingPermission("control")</code>.
631: */
632: @SuppressWarnings("this-escape")
633: public MailHandler(Properties props) {
634: init(props); //Must pass null or original object
635: }
636:
637: /**
638: * Check if this <code>Handler</code> would actually log a given
639: * <code>LogRecord</code> into its internal buffer.
640: * <p>
641: * This method checks if the <code>LogRecord</code> has an appropriate level
642: * and whether it satisfies any <code>Filter</code> including any
643: * attachment filters.
644: * However it does <b>not</b> check whether the <code>LogRecord</code> would
645: * result in a "push" of the buffer contents.
646: *
647: * @param record a <code>LogRecord</code> or null.
648: * @return true if the <code>LogRecord</code> would be logged.
649: */
650: @Override
651: public boolean isLoggable(final LogRecord record) {
652: //For a this-escape, level will be off.
653: //Unless subclass is known, the filter will be null
654: //and the attachment filters will be an empty array.
655: if (record == null) { //JDK-8233979
656: return false;
657: }
658:
659: int levelValue = getLevel().intValue();
660: if (record.getLevel().intValue() < levelValue || levelValue == offValue) {
661: return false;
662: }
663:
664: Filter body = getFilter();
665: if (body == null || body.isLoggable(record)) {
666: setMatchedPart(-1);
667: return true;
668: }
669:
670: return isAttachmentLoggable(record);
671: }
672:
673: /**
674: * Stores a <code>LogRecord</code> in the internal buffer.
675: * <p>
676: * The <code>isLoggable</code> method is called to check if the given log
677: * record is loggable. If the given record is loggable, it is copied into
678: * an internal buffer. Then the record's level property is compared with
679: * the push level. If the given level of the <code>LogRecord</code>
680: * is greater than or equal to the push level then the push filter is
681: * called. If no push filter exists, the push filter returns true,
682: * or the capacity of the internal buffer has been reached then all buffered
683: * records are formatted into one email and sent to the server.
684: *
685: * @param record description of the log event or null.
686: */
687: @Override
688: public void publish(final LogRecord record) {
689: /**
690: * It is possible for the handler to be closed after the
691: * call to isLoggable. In that case, the current thread
692: * will push to ensure that all published records are sent.
693: * See close().
694: */
695:
696: if (tryMutex()) {
697: try {
698: if (isLoggable(record)) {
699: if (record != null) {
700: record.getSourceMethodName(); //Infer caller.
701: publish0(record);
702: } else { //Override of isLoggable is broken.
703: reportNullError(ErrorManager.WRITE_FAILURE);
704: }
705: }
706: } catch (final LinkageError JDK8152515) {
707: reportLinkageError(JDK8152515, ErrorManager.WRITE_FAILURE);
708: } catch (final ServiceConfigurationError sce) {
709: reportConfigurationError(sce, ErrorManager.WRITE_FAILURE);
710: } finally {
711: releaseMutex();
712: }
713: } else {
714: reportUnPublishedError(record);
715: }
716: }
717:
718: /**
719: * Performs the publish after the record has been filtered.
720: *
721: * @param record the record which must not be null.
722: * @since JavaMail 1.4.5
723: */
724: private void publish0(final LogRecord record) {
725: Message msg;
726: boolean priority;
727: synchronized (this) {
728: //No need to check for sealed as long as the init method ensures
729: //that data.length and capacity are both zero until end of init().
730: //size is always zero on construction.
731: if (size == data.length && size < capacity) {
732: grow();
733: }
734:
735: if (size < data.length) {
736: //assert data.length == matched.length;
737: matched[size] = getMatchedPart();
738: data[size] = record;
739: ++size; //Be nice to client compiler.
740: priority = isPushable(record);
741: if (priority || size >= capacity) {
742: msg = writeLogRecords(ErrorManager.WRITE_FAILURE);
743: } else {
744: msg = null;
745: }
746: } else {
747: priority = false;
748: msg = null;
749: }
750: }
751:
752: if (msg != null) {
753: send(msg, priority, ErrorManager.WRITE_FAILURE);
754: }
755: }
756:
757: /**
758: * Report to the error manager that a logging loop was detected and
759: * we are going to break the cycle of messages. It is possible that
760: * a custom error manager could continue the cycle in which case
761: * we will stop trying to report errors.
762: *
763: * @param record the record or null.
764: * @since JavaMail 1.4.6
765: */
766: private void reportUnPublishedError(LogRecord record) {
767: final Integer idx = MUTEX.get();
768: if (idx == null || idx > MUTEX_REPORT) {
769: MUTEX.set(MUTEX_REPORT);
770: try {
771: final String msg;
772: if (record != null) {
773: final Formatter f = createSimpleFormatter();
774: msg = "Log record " + record.getSequenceNumber()
775: + " was not published. "
776: + head(f) + format(f, record) + tail(f, "");
777: } else {
778: msg = null;
779: }
780: Exception e = new IllegalStateException(
781: "Recursive publish detected by thread "
782: + Thread.currentThread());
783: reportError(msg, e, ErrorManager.WRITE_FAILURE);
784: } finally {
785: if (idx != null) {
786: MUTEX.set(idx);
787: } else {
788: MUTEX.remove();
789: }
790: }
791: }
792: }
793:
794: /**
795: * Used to detect reentrance by the current thread to the publish method.
796: * This mutex is thread local scope and will not block other threads.
797: * The state is advanced on if the current thread is in a reset state.
798: *
799: * @return true if the mutex was acquired.
800: * @since JavaMail 1.4.6
801: */
802: private boolean tryMutex() {
803: if (MUTEX.get() == null) {
804: MUTEX.set(MUTEX_PUBLISH);
805: return true;
806: } else {
807: return false;
808: }
809: }
810:
811: /**
812: * Releases the mutex held by the current thread.
813: * This mutex is thread local scope and will not block other threads.
814: *
815: * @since JavaMail 1.4.6
816: */
817: private void releaseMutex() {
818: MUTEX.remove();
819: }
820:
821: /**
822: * This is used to get the filter index from when {@code isLoggable} and
823: * {@code isAttachmentLoggable} was invoked by {@code publish} method.
824: *
825: * @return the filter index or MUTEX_PUBLISH if unknown.
826: * @throws NullPointerException if tryMutex was not called.
827: * @since JavaMail 1.5.5
828: */
829: private int getMatchedPart() {
830: //assert Thread.holdsLock(this);
831: Integer idx = MUTEX.get();
832: if (idx == null || idx >= readOnlyAttachmentFilters().length) {
833: idx = MUTEX_PUBLISH;
834: }
835: return idx;
836: }
837:
838: /**
839: * This is used to record the filter index when {@code isLoggable} and
840: * {@code isAttachmentLoggable} was invoked by {@code publish} method.
841: *
842: * @param index the filter index.
843: * @since JavaMail 1.5.5
844: */
845: private void setMatchedPart(int index) {
846: if (MUTEX_PUBLISH.equals(MUTEX.get())) {
847: MUTEX.set(index);
848: }
849: }
850:
851: /**
852: * Clear previous matches when the filters are modified and there are
853: * existing log records that were matched.
854: *
855: * @param index the lowest filter index to clear.
856: * @since JavaMail 1.5.5
857: */
858: private void clearMatches(int index) {
859: assert Thread.holdsLock(this);
860: for (int r = 0; r < size; ++r) {
861: if (matched[r] >= index) {
862: matched[r] = MUTEX_PUBLISH;
863: }
864: }
865: }
866:
867: /**
868: * A callback method for when this object is about to be placed into
869: * commission. This contract is defined by the
870: * {@code org.glassfish.hk2.api.PostConstruct} interface. If this class is
871: * loaded via a lifecycle managed environment other than HK2 then it is
872: * recommended that this method is called either directly or through
873: * extending this class to signal that this object is ready for use.
874: *
875: * @since JavaMail 1.5.3
876: */
877: //@javax.annotation.PostConstruct
878: public void postConstruct() {
879: }
880:
881: /**
882: * A callback method for when this object is about to be decommissioned.
883: * This contract is defined by the {@code org.glassfish.hk2.api.PreDestory}
884: * interface. If this class is loaded via a lifecycle managed environment
885: * other than HK2 then it is recommended that this method is called either
886: * directly or through extending this class to signal that this object will
887: * be destroyed.
888: *
889: * @since JavaMail 1.5.3
890: */
891: //@javax.annotation.PreDestroy
892: public void preDestroy() {
893: /**
894: * Close can require permissions so just trigger a push.
895: */
896: push(false, ErrorManager.CLOSE_FAILURE);
897: }
898:
899: /**
900: * Pushes any buffered records to the email server as high importance with
901: * urgent priority. The internal buffer is then cleared. Does nothing if
902: * called from inside a push.
903: *
904: * @see #flush()
905: */
906: public void push() {
907: push(true, ErrorManager.FLUSH_FAILURE);
908: }
909:
910: /**
911: * Pushes any buffered records to the email server as normal priority.
912: * The internal buffer is then cleared. Does nothing if called from inside
913: * a push.
914: *
915: * @see #push()
916: */
917: @Override
918: public void flush() {
919: push(false, ErrorManager.FLUSH_FAILURE);
920: }
921:
922: /**
923: * Prevents any other records from being published.
924: * Pushes any buffered records to the email server as normal priority.
925: * The internal buffer is then cleared. Once this handler is closed it
926: * will remain closed.
927: * <p>
928: * If this <code>Handler</code> is only implicitly closed by the
929: * <code>LogManager</code>, then <a href="#verify">verification</a> should
930: * be turned on and or <code>mailEntries</code> should be declared to define
931: * the mail properties.
932: *
933: * @throws SecurityException if a security manager exists and the
934: * caller does not have <code>LoggingPermission("control")</code>.
935: * @see #flush()
936: */
937: @Override
938: public void close() {
939: checkAccess();
940: try {
941: Message msg = null;
942: synchronized (this) {
943: try {
944: msg = writeLogRecords(ErrorManager.CLOSE_FAILURE);
945: } finally { //Change level after formatting.
946: this.logLevel = Level.OFF;
947: this.disabledLevel = null; //free reference
948: /**
949: * The sign bit of the capacity is set to ensure that
950: * records that have passed isLoggable, but have yet to be
951: * added to the internal buffer, are immediately pushed as
952: * an email.
953: */
954: if (this.capacity > 0) {
955: this.capacity = -this.capacity;
956: }
957:
958: //Only need room for one record after closed
959: //Ensure not inside a push.
960: if (size == 0 && data.length != 1) {
961: initLogRecords(1);
962: }
963: }
964: }
965:
966: if (msg != null) {
967: send(msg, false, ErrorManager.CLOSE_FAILURE);
968: }
969: } catch (final LinkageError JDK8152515) {
970: reportLinkageError(JDK8152515, ErrorManager.CLOSE_FAILURE);
971: } catch (final ServiceConfigurationError sce) {
972: reportConfigurationError(sce, ErrorManager.CLOSE_FAILURE);
973: }
974: }
975:
976: /**
977: * Gets the enabled status of this handler.
978: *
979: * @return true if this handler is accepting log records.
980: * @see #setEnabled(boolean)
981: * @see #setLevel(java.util.logging.Level)
982: * @since Angus Mail 2.0.3
983: */
984: public synchronized boolean isEnabled() {
985: //For a this-escape, capacity will be zero and level will be off.
986: //No need to check that construction completed.
987: return capacity > 0 && this.logLevel.intValue() != offValue;
988: }
989:
990: /**
991: * Used to enable or disable this handler.
992: *
993: * Pushes any buffered records to the email server as normal priority.
994: * The internal buffer is then cleared.
995: *
996: * @param enabled true to enable and false to disable.
997: * @throws SecurityException if a security manager exists and if the caller
998: * does not have <code>LoggingPermission("control")</code>.
999: * @see #flush()
1000: * @see #isEnabled()
1001: * @since Angus Mail 2.0.3
1002: */
1003: public synchronized void setEnabled(final boolean enabled) {
1004: checkAccess();
1005: if (this.capacity > 0) { //handler is open
1006: if (this.size != 0) {
1007: push(false, ErrorManager.FLUSH_FAILURE);
1008: }
1009: if (enabled) {
1010: if (this.disabledLevel != null) { //was disabled
1011: this.logLevel = this.disabledLevel;
1012: this.disabledLevel = null;
1013: }
1014: } else {
1015: if (this.disabledLevel == null) {
1016: this.disabledLevel = this.logLevel;
1017: this.logLevel = Level.OFF;
1018: }
1019: }
1020: }
1021: }
1022:
1023: /**
1024: * Set the log level specifying which message levels will be
1025: * logged by this <code>Handler</code>. Message levels lower than this
1026: * value will be discarded.
1027: *
1028: * @param newLevel the new value for the log level
1029: * @throws NullPointerException if <code>newLevel</code> is
1030: * <code>null</code>.
1031: * @throws SecurityException if a security manager exists and
1032: * the caller does not have
1033: * <code>LoggingPermission("control")</code>.
1034: */
1035: @Override
1036: public void setLevel(final Level newLevel) {
1037: Objects.requireNonNull(newLevel);
1038: checkAccess();
1039:
1040: //Don't allow a closed handler to be opened (half way).
1041: synchronized (this) { //Wait for writeLogRecords.
1042: if (this.capacity > 0) {
1043: //if disabled then track the new level to be used when enabled.
1044: if (this.disabledLevel != null) {
1045: this.disabledLevel = newLevel;
1046: } else {
1047: this.logLevel = newLevel;
1048: }
1049: }
1050: }
1051: }
1052:
1053: /**
1054: * Get the log level specifying which messages will be logged by this
1055: * <code>Handler</code>. Message levels lower than this level will be
1056: * discarded.
1057: *
1058: * @return the level of messages being logged.
1059: */
1060: @Override
1061: public Level getLevel() {
1062: //For a this-escape, this value will be OFF.
1063: //No need to check that construction completed.
1064: return logLevel; //Volatile access.
1065: }
1066:
1067: /**
1068: * Retrieves the ErrorManager for this Handler.
1069: *
1070: * @return the ErrorManager for this Handler
1071: * @throws SecurityException if a security manager exists and if the caller
1072: * does not have <code>LoggingPermission("control")</code>.
1073: */
1074: @Override
1075: public ErrorManager getErrorManager() {
1076: checkAccess();
1077: return this.errorManager; //Volatile access.
1078: }
1079:
1080: /**
1081: * Define an ErrorManager for this Handler.
1082: * <p>
1083: * The ErrorManager's "error" method will be invoked if any errors occur
1084: * while using this Handler.
1085: *
1086: * @param em the new ErrorManager
1087: * @throws SecurityException if a security manager exists and if the
1088: * caller does not have <code>LoggingPermission("control")</code>.
1089: * @throws NullPointerException if the given error manager is null.
1090: */
1091: @Override
1092: public void setErrorManager(final ErrorManager em) {
1093: checkAccess();
1094: setErrorManager0(em);
1095: }
1096:
1097: /**
1098: * Sets the error manager on this handler and the super handler. In secure
1099: * environments the super call may not be allowed which is not a failure
1100: * condition as it is an attempt to free the unused handler error manager.
1101: *
1102: * @param em a non null error manager.
1103: * @throws NullPointerException if the given error manager is null.
1104: * @since JavaMail 1.5.6
1105: */
1106: private void setErrorManager0(final ErrorManager em) {
1107: Objects.requireNonNull(em);
1108: try {
1109: synchronized (this) { //Wait for writeLogRecords.
1110: this.errorManager = em;
1111: super.setErrorManager(em); //Try to free super error manager.
1112: }
1113: } catch (RuntimeException | LinkageError ignore) {
1114: }
1115: }
1116:
1117: /**
1118: * Get the current <code>Filter</code> for this <code>Handler</code>.
1119: *
1120: * @return a <code>Filter</code> object (may be null)
1121: */
1122: @Override
1123: public Filter getFilter() {
1124: return this.filter; //Volatile access.
1125: }
1126:
1127: /**
1128: * Set a <code>Filter</code> to control output on this <code>Handler</code>.
1129: * <P>
1130: * For each call of <code>publish</code> the <code>Handler</code> will call
1131: * this <code>Filter</code> (if it is non-null) to check if the
1132: * <code>LogRecord</code> should be published or discarded.
1133: *
1134: * @param newFilter a <code>Filter</code> object (may be null)
1135: * @throws SecurityException if a security manager exists and if the caller
1136: * does not have <code>LoggingPermission("control")</code>.
1137: */
1138: @Override
1139: public void setFilter(final Filter newFilter) {
1140: checkAccess();
1141: synchronized (this) { //Wait for writeLogRecords.
1142: if (newFilter != filter) {
1143: clearMatches(-1);
1144: }
1145: this.filter = newFilter; //Volatile access.
1146: }
1147: }
1148:
1149: /**
1150: * Return the character encoding for this <code>Handler</code>.
1151: *
1152: * @return The encoding name. May be null, which indicates the default
1153: * encoding should be used.
1154: */
1155: @Override
1156: public synchronized String getEncoding() {
1157: //For a this-escape, this value will be null.
1158: //No need to check that construction completed.
1159: return this.encoding;
1160: }
1161:
1162: /**
1163: * Set the character encoding used by this <code>Handler</code>.
1164: * <p>
1165: * The encoding should be set before any <code>LogRecords</code> are written
1166: * to the <code>Handler</code>.
1167: *
1168: * @param encoding The name of a supported character encoding. May be
1169: * null, to indicate the default platform encoding.
1170: * @throws SecurityException if a security manager exists and if the caller
1171: * does not have <code>LoggingPermission("control")</code>.
1172: * @throws UnsupportedEncodingException if the named encoding is not
1173: * supported.
1174: */
1175: @Override
1176: public void setEncoding(String encoding) throws UnsupportedEncodingException {
1177: checkAccess();
1178: setEncoding0(encoding);
1179: }
1180:
1181: /**
1182: * Set the character encoding used by this handler. This method does not
1183: * check permissions of the caller.
1184: *
1185: * @param e any encoding name or null for the default.
1186: * @throws UnsupportedEncodingException if the given encoding is not supported.
1187: */
1188: private void setEncoding0(String e) throws UnsupportedEncodingException {
1189: if (e != null) {
1190: try {
1191: if (!java.nio.charset.Charset.isSupported(e)) {
1192: throw new UnsupportedEncodingException(e);
1193: }
1194: } catch (java.nio.charset.IllegalCharsetNameException icne) {
1195: throw new UnsupportedEncodingException(e);
1196: }
1197: }
1198:
1199: synchronized (this) { //Wait for writeLogRecords.
1200: this.encoding = e;
1201: }
1202: }
1203:
1204: /**
1205: * Return the <code>Formatter</code> for this <code>Handler</code>.
1206: *
1207: * @return the <code>Formatter</code> (may be null).
1208: */
1209: @Override
1210: public synchronized Formatter getFormatter() {
1211: return this.formatter;
1212: }
1213:
1214: /**
1215: * Set a <code>Formatter</code>. This <code>Formatter</code> will be used
1216: * to format <code>LogRecords</code> for this <code>Handler</code>.
1217: * <p>
1218: * Some <code>Handlers</code> may not use <code>Formatters</code>, in which
1219: * case the <code>Formatter</code> will be remembered, but not used.
1220: *
1221: * @param newFormatter the <code>Formatter</code> to use (may not be null)
1222: * @throws SecurityException if a security manager exists and if the caller
1223: * does not have <code>LoggingPermission("control")</code>.
1224: * @throws NullPointerException if the given formatter is null.
1225: */
1226: @Override
1227: public synchronized void setFormatter(Formatter newFormatter) throws SecurityException {
1228: checkAccess();
1229: this.formatter = Objects.requireNonNull(newFormatter);
1230: }
1231:
1232: /**
1233: * Gets the push level. The default is <code>Level.OFF</code> meaning that
1234: * this <code>Handler</code> will only push when the internal buffer is full.
1235: *
1236: * @return a non-null push level.
1237: */
1238: public final synchronized Level getPushLevel() {
1239: return this.pushLevel;
1240: }
1241:
1242: /**
1243: * Sets the push level. This level is used to trigger a push so that
1244: * all pending records are formatted and sent to the email server. When
1245: * the push level triggers a send, the resulting email is flagged as
1246: * high importance with urgent priority.
1247: *
1248: * @param level any level object or null meaning off.
1249: * @throws SecurityException if a security manager exists and the
1250: * caller does not have <code>LoggingPermission("control")</code>.
1251: * @throws IllegalStateException if called from inside a push.
1252: */
1253: public final synchronized void setPushLevel(Level level) {
1254: checkAccess();
1255: if (level == null) {
1256: level = Level.OFF;
1257: }
1258:
1259: if (isWriting) {
1260: throw new IllegalStateException();
1261: }
1262: this.pushLevel = level;
1263: }
1264:
1265: /**
1266: * Gets the push filter. The default is <code>null</code>.
1267: *
1268: * @return the push filter or <code>null</code>.
1269: */
1270: public final synchronized Filter getPushFilter() {
1271: return this.pushFilter;
1272: }
1273:
1274: /**
1275: * Sets the push filter. This filter is only called if the given
1276: * <code>LogRecord</code> level was greater than the push level. If this
1277: * filter returns <code>true</code>, all pending records are formatted and
1278: * sent to the email server. When the push filter triggers a send, the
1279: * resulting email is flagged as high importance with urgent priority.
1280: *
1281: * @param filter push filter or <code>null</code>
1282: * @throws SecurityException if a security manager exists and the
1283: * caller does not have <code>LoggingPermission("control")</code>.
1284: * @throws IllegalStateException if called from inside a push.
1285: */
1286: public final synchronized void setPushFilter(final Filter filter) {
1287: checkAccess();
1288: if (isWriting) {
1289: throw new IllegalStateException();
1290: }
1291: this.pushFilter = filter;
1292: }
1293:
1294: /**
1295: * Gets the comparator used to order all <code>LogRecord</code> objects
1296: * prior to formatting. If <code>null</code> then the order is unspecified.
1297: *
1298: * @return the <code>LogRecord</code> comparator.
1299: */
1300: public final synchronized Comparator<? super LogRecord> getComparator() {
1301: return this.comparator;
1302: }
1303:
1304: /**
1305: * Sets the comparator used to order all <code>LogRecord</code> objects
1306: * prior to formatting. If <code>null</code> then the order is unspecified.
1307: *
1308: * @param c the <code>LogRecord</code> comparator.
1309: * @throws SecurityException if a security manager exists and the
1310: * caller does not have <code>LoggingPermission("control")</code>.
1311: * @throws IllegalStateException if called from inside a push.
1312: */
1313: public final synchronized void setComparator(Comparator<? super LogRecord> c) {
1314: checkAccess();
1315: if (isWriting) {
1316: throw new IllegalStateException();
1317: }
1318: this.comparator = c;
1319: }
1320:
1321: /**
1322: * Gets the number of log records the internal buffer can hold. When
1323: * capacity is reached, <code>Handler</code> will format all
1324: * <code>LogRecord</code> objects into one email message.
1325: *
1326: * @return the capacity.
1327: */
1328: public final synchronized int getCapacity() {
1329: assert capacity != Integer.MIN_VALUE : capacity;
1330: return capacity != 0 ? Math.abs(capacity) : DEFAULT_CAPACITY;
1331: }
1332:
1333: /**
1334: * Sets the capacity for this handler.
1335: *
1336: * Pushes any buffered records to the email server as normal priority.
1337: * The internal buffer is then cleared.
1338: *
1339: * @param newCapacity the max number of records. The default capacity of
1340: * 1000 is used if the given capacity is less than one.
1341: * @throws SecurityException if a security manager exists and the caller
1342: * does not have <code>LoggingPermission("control")</code>.
1343: * @throws IllegalStateException if called from inside a push.
1344: * @see #flush()
1345: * @since Angus Mail 2.0.3
1346: */
1347: public final synchronized void setCapacity(int newCapacity) {
1348: checkAccess();
1349: setCapacity0(newCapacity);
1350: }
1351:
1352: /**
1353: * Gets the <code>Authenticator</code> used to login to the email server.
1354: *
1355: * @return an <code>Authenticator</code> or <code>null</code> if none is
1356: * required.
1357: * @throws SecurityException if a security manager exists and the
1358: * caller does not have <code>LoggingPermission("control")</code>.
1359: */
1360: public final synchronized Authenticator getAuthenticator() {
1361: checkAccess();
1362: return this.auth;
1363: }
1364:
1365: /**
1366: * Sets the <code>Authenticator</code> used to login to the email server.
1367: *
1368: * @param auth an <code>Authenticator</code> object or null if none is
1369: * required.
1370: * @throws SecurityException if a security manager exists and the
1371: * caller does not have <code>LoggingPermission("control")</code>.
1372: * @throws IllegalStateException if called from inside a push.
1373: */
1374: public final void setAuthenticator(final Authenticator auth) {
1375: this.setAuthenticator0(auth);
1376: }
1377:
1378: /**
1379: * Sets the <code>Authenticator</code> used to login to the email server.
1380: *
1381: * @param password a password, empty array can be used to only supply a
1382: * user name set by <code>mail.user</code> property, or null if no
1383: * credentials are required.
1384: * @throws SecurityException if a security manager exists and the
1385: * caller does not have <code>LoggingPermission("control")</code>.
1386: * @throws IllegalStateException if called from inside a push.
1387: * @see String#toCharArray()
1388: * @since JavaMail 1.4.6
1389: */
1390: public final void setAuthenticator(final char... password) {
1391: if (password == null) {
1392: setAuthenticator0((Authenticator) null);
1393: } else {
1394: setAuthenticator0(DefaultAuthenticator.of(new String(password)));
1395: }
1396: }
1397:
1398: /**
1399: * Sets the <code>Authenticator</code> class name or password used to login
1400: * to the email server.
1401: *
1402: * @param auth the class name of the authenticator, literal password, or
1403: * empty string can be used to only supply a user name set by
1404: * <code>mail.user</code> property. A null value can be used if no
1405: * credentials are required.
1406: * @throws SecurityException if a security manager exists and the caller
1407: * does not have <code>LoggingPermission("control")</code>.
1408: * @throws IllegalStateException if called from inside a push.
1409: * @see #getAuthenticator()
1410: * @see #setAuthenticator(char...)
1411: * @since Angus Mail 2.0.3
1412: */
1413: public final synchronized void setAuthentication(final String auth) {
1414: setAuthenticator0(newAuthenticator(auth));
1415: }
1416:
1417: /**
1418: * A private hook to handle possible future overrides. See public method.
1419: *
1420: * @param auth see public method.
1421: * @throws SecurityException if a security manager exists and the
1422: * caller does not have <code>LoggingPermission("control")</code>.
1423: * @throws IllegalStateException if called from inside a push.
1424: */
1425: private void setAuthenticator0(final Authenticator auth) {
1426: checkAccess();
1427:
1428: Session settings;
1429: synchronized (this) {
1430: if (isWriting) {
1431: throw new IllegalStateException();
1432: }
1433: this.auth = auth;
1434: settings = updateSession();
1435: }
1436: verifySettings(settings);
1437: }
1438:
1439: /**
1440: * Sets the mail properties used for the session. The key/value pairs
1441: * are defined in the <code>Java Mail API</code> documentation. This
1442: * <code>Handler</code> will also search the <code>LogManager</code> for
1443: * defaults if needed. A key named <code>verify</code> can be declared to
1444: * trigger <a href="#verify">verification</a>.
1445: *
1446: * @param props properties object or null. A null value will supply the
1447: * <code>mailEntries</code> from the <code>LogManager</code>. An empty
1448: * properties will clear all existing mail properties assigned to this
1449: * handler.
1450: * @throws SecurityException if a security manager exists and the
1451: * caller does not have <code>LoggingPermission("control")</code>.
1452: * @throws IllegalStateException if called from inside a push.
1453: */
1454: public final void setMailProperties(Properties props) {
1455: checkAccess();
1456: if (props == null) {
1457: final String p = getClass().getName();
1458: props = parseProperties(
1459: fromLogManager(p.concat(".mailEntries")));
1460: setMailProperties0(props != null ? props : new Properties());
1461: } else {
1462: setMailProperties0(copyOf(props));
1463: }
1464: }
1465:
1466: /**
1467: * Copies a properties object. Checks that given properties clone
1468: * returns the a Properties object and that it is not null.
1469: *
1470: * @param props a properties object
1471: * @return a copy of the properties object.
1472: * @throws ClassCastException if clone doesn't return a Properties object.
1473: * @throws NullPointerExeption if props is null or if the copy was null.
1474: * @since Angus Mail 2.0.3
1475: */
1476: private Properties copyOf(Properties props) {
1477: //Allow subclasses however, check that clone contract was followed in
1478: //that a non-null Properties object was returned.
1479: //No need to perform reflexive test or exact class matching tests as
1480: //that doesn't really cause any unexpected failures.
1481: Properties copy = (Properties) props.clone();
1482: return Objects.requireNonNull(copy,
1483: props.getClass().getName());
1484: }
1485:
1486: /**
1487: * A private hook to set and validate properties.
1488: * See public method for details.
1489: *
1490: * @param props a safe properties object.
1491: * @return true if verification key was present.
1492: * @throws NullPointerException if props is null.
1493: */
1494: private boolean setMailProperties0(Properties props) {
1495: Objects.requireNonNull(props);
1496: Session settings;
1497: synchronized (this) {
1498: if (isWriting) {
1499: throw new IllegalStateException();
1500: }
1501: this.mailProps = props;
1502: settings = updateSession();
1503: }
1504: return verifySettings(settings);
1505: }
1506:
1507: /**
1508: * Gets a copy of the mail properties used for the session.
1509: *
1510: * @return a non null properties object.
1511: * @throws SecurityException if a security manager exists and the
1512: * caller does not have <code>LoggingPermission("control")</code>.
1513: */
1514: public final Properties getMailProperties() {
1515: checkAccess();
1516: final Properties props;
1517: synchronized (this) {
1518: props = this.mailProps;
1519: }
1520:
1521: //Null check to force an error sooner rather than later.
1522: return Objects.requireNonNull((Properties) props.clone());
1523: }
1524:
1525: /**
1526: * Parses the given properties lines then clears and sets all of the mail
1527: * properties used for the session. Any parsing errors are reported to the
1528: * error manager. This method provides bean style properties support.
1529: * <p>
1530: * The given string should be treated as lines of a properties file. The
1531: * character {@code '='} or {@code ':'} are used to separate an entry also
1532: * known as a key/value pair. The line terminator characters {@code \r} or
1533: * {@code \n} or {@code \r\n} are used to separate each entry. The
1534: * characters {@code '#!'} together can be used to signal the end of an
1535: * entry when escape characters are not supported.
1536: * <p>
1537: * The example from the <a href="#configuration">configuration</a>
1538: * section would be formatted as the following string:
1539: * <pre>
1540: * mail.smtp.host:my-mail-server#!mail.to:me@example.com#!verify:local
1541: * </pre>
1542: * <p>
1543: * The key/value pairs are defined in the <code>Java Mail API</code>
1544: * documentation. This <code>Handler</code> will also search the
1545: * <code>LogManager</code> for defaults if needed. A key named
1546: * <code>verify</code> can be declared to trigger
1547: * <a href="#verify">verification</a>.
1548: *
1549: * @param entries one or more key/value pairs. A null value will supply the
1550: * <code>mailEntries</code> from the <code>LogManager</code>. An empty
1551: * string or the literal null are all treated as empty properties and will
1552: * clear all existing mail properties assigned to this handler.
1553: * @throws SecurityException if a security manager exists and the caller
1554: * does not have <code>LoggingPermission("control")</code>.
1555: * @throws IllegalStateException if called from inside a push.
1556: * @see #getMailProperties()
1557: * @see java.io.StringReader
1558: * @see java.util.Properties#load(Reader)
1559: * @since Angus Mail 2.0.3
1560: */
1561: public final void setMailEntries(String entries) {
1562: checkAccess();
1563: if (entries == null) {
1564: final String p = getClass().getName();
1565: entries = fromLogManager(p.concat(".mailEntries"));
1566: }
1567: final Properties props = parseProperties(entries);
1568: setMailProperties0(props != null ? props : new Properties());
1569: }
1570:
1571: /**
1572: * Formats the current mail properties as properties lines. Any formatting
1573: * errors are reported to the error manager. The returned string should be
1574: * treated as lines of a properties file. The value of this string is
1575: * reconstructed from the properties object and therefore may be different
1576: * from what was originally set. This method provides bean style properties
1577: * support.
1578: *
1579: * @return string representation of the mail properties.
1580: * @throws SecurityException if a security manager exists and the caller
1581: * does not have <code>LoggingPermission("control")</code>.
1582: * @throws IllegalStateException if called from inside a push.
1583: * @see #getMailProperties()
1584: * @see java.io.StringWriter
1585: * @see java.util.Properties#store(java.io.Writer, java.lang.String)
1586: * @since Angus Mail 2.0.3
1587: */
1588: public final String getMailEntries() {
1589: checkAccess();
1590: final Properties props;
1591: synchronized (this) {
1592: props = this.mailProps;
1593: }
1594:
1595: final StringWriter sw = new StringWriter();
1596: try {
1597: //Dynamic cast used so byte code verifier doesn't load StringWriter
1598: props.store(Writer.class.cast(sw), (String) null);
1599: } catch (IOException | RuntimeException ex) {
1600: reportError(props.toString(), ex, ErrorManager.GENERIC_FAILURE);
1601: //partially constructed values are allowed to be returned
1602: }
1603:
1604: //Properties.store will always write a date comment
1605: //which is removed by this code.
1606: String entries = sw.toString();
1607: if (entries.startsWith("#")) {
1608: String sep = System.lineSeparator();
1609: int end = entries.indexOf(sep);
1610: if (end > 0) {
1611: entries = entries.substring(end + sep.length(), entries.length());
1612: }
1613: }
1614: return entries;
1615: }
1616:
1617: /**
1618: * Gets the attachment filters. If the attachment filter does not
1619: * allow any <code>LogRecord</code> to be formatted, the attachment may
1620: * be omitted from the email.
1621: *
1622: * @return a non null array of attachment filters.
1623: */
1624: public final Filter[] getAttachmentFilters() {
1625: return readOnlyAttachmentFilters().clone();
1626: }
1627:
1628: /**
1629: * Sets the attachment filters.
1630: *
1631: * @param filters array of filters. A <code>null</code> array is treated
1632: * the same as an empty array and will remove all attachments. A
1633: * <code>null</code> index value means that all records are allowed for the
1634: * attachment at that index.
1635: * @throws SecurityException if a security manager exists and the
1636: * caller does not have <code>LoggingPermission("control")</code>.
1637: * @throws IllegalStateException if called from inside a push.
1638: */
1639: public final void setAttachmentFilters(Filter... filters) {
1640: checkAccess();
1641: if (filters == null || filters.length == 0) {
1642: filters = emptyFilterArray();
1643: } else {
1644: filters = Arrays.copyOf(filters, filters.length, Filter[].class);
1645: }
1646:
1647: synchronized (this) {
1648: if (isWriting) {
1649: throw new IllegalStateException();
1650: }
1651:
1652: if (size != 0) {
1653: final int len = Math.min(filters.length, attachmentFilters.length);
1654: int i = 0;
1655: for (; i < len; ++i) {
1656: if (filters[i] != attachmentFilters[i]) {
1657: break;
1658: }
1659: }
1660: clearMatches(i);
1661: }
1662: this.attachmentFilters = filters;
1663: this.alignAttachmentFormatters(filters.length);
1664: this.alignAttachmentNames(filters.length);
1665: }
1666: }
1667:
1668: /**
1669: * Gets the attachment formatters. This <code>Handler</code> is using
1670: * attachments only if the returned array length is non zero.
1671: *
1672: * @return a non <code>null</code> array of formatters.
1673: */
1674: public final Formatter[] getAttachmentFormatters() {
1675: Formatter[] formatters;
1676: synchronized (this) {
1677: formatters = this.attachmentFormatters;
1678: }
1679: return formatters.clone();
1680: }
1681:
1682: /**
1683: * Sets the attachment <code>Formatter</code> object for this handler.
1684: * The number of formatters determines the number of attachments per
1685: * email. This method should be the first attachment method called.
1686: * To remove all attachments, call this method with empty array.
1687: *
1688: * @param formatters an array of formatters. A null array is treated as an
1689: * empty array. Any null indexes is replaced with a
1690: * {@linkplain java.util.logging.SimpleFormatter SimpleFormatter}.
1691: * @throws SecurityException if a security manager exists and the
1692: * caller does not have <code>LoggingPermission("control")</code>.
1693: * @throws IllegalStateException if called from inside a push.
1694: */
1695: public final void setAttachmentFormatters(Formatter... formatters) {
1696: checkAccess();
1697: if (formatters == null || formatters.length == 0) { //Null check and length check.
1698: formatters = emptyFormatterArray();
1699: } else {
1700: formatters = Arrays.copyOf(formatters,
1701: formatters.length, Formatter[].class);
1702: for (int i = 0; i < formatters.length; ++i) {
1703: if (formatters[i] == null) {
1704: formatters[i] = createSimpleFormatter();
1705: }
1706: }
1707: }
1708:
1709: synchronized (this) {
1710: if (isWriting) {
1711: throw new IllegalStateException();
1712: }
1713:
1714: this.attachmentFormatters = formatters;
1715: this.alignAttachmentFilters(formatters.length);
1716: this.alignAttachmentNames(formatters.length);
1717: }
1718: }
1719:
1720: /**
1721: * Gets the attachment name formatters.
1722: * If the attachment names were set using explicit names then
1723: * the names can be returned by calling <code>toString</code> on each
1724: * attachment name formatter.
1725: *
1726: * @return non <code>null</code> array of attachment name formatters.
1727: */
1728: public final Formatter[] getAttachmentNames() {
1729: final Formatter[] formatters;
1730: synchronized (this) {
1731: formatters = this.attachmentNames;
1732: }
1733: return formatters.clone();
1734: }
1735:
1736: /**
1737: * Sets the attachment file name for each attachment. All control
1738: * characters are removed from the attachment names.
1739: * This method will create a set of custom formatters.
1740: *
1741: * @param names an array of names. A null array is treated as an empty
1742: * array. Any null or empty indexes are replaced with the string
1743: * representation of the attachment formatter.
1744: * @throws SecurityException if a security manager exists and the
1745: * caller does not have <code>LoggingPermission("control")</code>.
1746: * @throws IllegalStateException if called from inside a push.
1747: * @see Character#isISOControl(char)
1748: * @see Character#isISOControl(int)
1749: */
1750:
1751: public final void setAttachmentNames(final String... names) {
1752: checkAccess();
1753:
1754: final Formatter[] formatters;
1755: if (names == null || names.length == 0) {
1756: formatters = emptyFormatterArray();
1757: } else {
1758: formatters = new Formatter[names.length];
1759: }
1760:
1761: synchronized (this) {
1762: if (isWriting) {
1763: throw new IllegalStateException();
1764: }
1765:
1766: this.alignAttachmentFormatters(formatters.length);
1767: this.alignAttachmentFilters(formatters.length);
1768: for (int i = 0; i < formatters.length; ++i) {
1769: //names is non-null if formatters length is not zero
1770: String name = names[i];
1771: if (isEmpty(name)) {
1772: name = toString(this.attachmentFormatters[i]);
1773: }
1774: formatters[i] = TailNameFormatter.of(name);
1775: }
1776: this.attachmentNames = formatters;
1777: }
1778: }
1779:
1780: /**
1781: * Sets the attachment file name formatters. The format method of each
1782: * attachment formatter will see only the <code>LogRecord</code> objects
1783: * that passed its attachment filter during formatting. The format method
1784: * will typically return an empty string. Instead of being used to format
1785: * records, it is used to gather information about the contents of an
1786: * attachment. The <code>getTail</code> method should be used to construct
1787: * the attachment file name and reset any formatter collected state. All
1788: * control characters will be removed from the output of the formatter. The
1789: * <code>toString</code> method of the given formatter should be overridden
1790: * to provide a useful attachment file name, if possible.
1791: *
1792: * @param formatters and array of attachment name formatters.
1793: * @throws SecurityException if a security manager exists and the
1794: * caller does not have <code>LoggingPermission("control")</code>.
1795: * @throws IllegalStateException if called from inside a push.
1796: * @see Character#isISOControl(char)
1797: * @see Character#isISOControl(int)
1798: */
1799: public final void setAttachmentNames(Formatter... formatters) {
1800: setAttachmentNameFormatters(formatters);
1801: }
1802:
1803: /**
1804: * Sets the attachment file name formatters. The format method of each
1805: * attachment formatter will see only the <code>LogRecord</code> objects
1806: * that passed its attachment filter during formatting. The format method
1807: * will typically return an empty string. Instead of being used to format
1808: * records, it is used to gather information about the contents of an
1809: * attachment. The <code>getTail</code> method should be used to construct
1810: * the attachment file name and reset any formatter collected state. All
1811: * control characters will be removed from the output of the formatter. The
1812: * <code>toString</code> method of the given formatter should be overridden
1813: * to provide a useful attachment file name, if possible.
1814: *
1815: * @param formatters and array of attachment name formatters.
1816: * @throws SecurityException if a security manager exists and the
1817: * caller does not have <code>LoggingPermission("control")</code>.
1818: * @throws IllegalStateException if called from inside a push.
1819: * @see Character#isISOControl(char)
1820: * @see Character#isISOControl(int)
1821: * @since Angus Mail 2.0.3
1822: */
1823: public final void setAttachmentNameFormatters(Formatter... formatters) {
1824: checkAccess();
1825:
1826: if (formatters == null || formatters.length == 0) {
1827: formatters = emptyFormatterArray();
1828: } else {
1829: formatters = Arrays.copyOf(formatters, formatters.length,
1830: Formatter[].class);
1831: }
1832:
1833: synchronized (this) {
1834: if (isWriting) {
1835: throw new IllegalStateException();
1836: }
1837:
1838: this.alignAttachmentFormatters(formatters.length);
1839: this.alignAttachmentFilters(formatters.length);
1840: for (int i = 0; i < formatters.length; ++i) {
1841: if (formatters[i] == null) {
1842: formatters[i] = TailNameFormatter.of(toString(this.attachmentFormatters[i]));
1843: }
1844: }
1845: this.attachmentNames = formatters;
1846: }
1847: }
1848:
1849: /**
1850: * Gets the formatter used to create the subject line.
1851: * If the subject was created using a literal string then
1852: * the <code>toString</code> method can be used to get the subject line.
1853: *
1854: * @return the formatter.
1855: */
1856: public final synchronized Formatter getSubject() {
1857: return getSubjectFormatter();
1858: }
1859:
1860: /**
1861: * Gets the formatter used to create the subject line.
1862: * If the subject was created using a literal string then
1863: * the <code>toString</code> method can be used to get the subject line.
1864: *
1865: * @return the formatter.
1866: * @since Angus Mail 2.0.3
1867: */
1868: public final synchronized Formatter getSubjectFormatter() {
1869: return this.subjectFormatter;
1870: }
1871:
1872: /**
1873: * Sets a literal string for the email subject. All control characters are
1874: * removed from the subject line of the email
1875: *
1876: * @param subject a non <code>null</code> string.
1877: * @throws SecurityException if a security manager exists and the
1878: * caller does not have <code>LoggingPermission("control")</code>.
1879: * @throws NullPointerException if <code>subject</code> is
1880: * <code>null</code>.
1881: * @throws IllegalStateException if called from inside a push.
1882: * @see Character#isISOControl(char)
1883: * @see Character#isISOControl(int)
1884: */
1885: public synchronized final void setSubject(final String subject) {
1886: if (subject != null) {
1887: this.setSubjectFormatter(TailNameFormatter.of(subject));
1888: } else {
1889: checkAccess();
1890: initSubject((String) null);
1891: }
1892: }
1893:
1894: /**
1895: * Sets the subject formatter for email. The format method of the subject
1896: * formatter will see all <code>LogRecord</code> objects that were published
1897: * to this <code>Handler</code> during formatting and will typically return
1898: * an empty string. This formatter is used to gather information to create
1899: * a summary about what information is contained in the email. The
1900: * <code>getTail</code> method should be used to construct the subject and
1901: * reset any formatter collected state. All control characters
1902: * will be removed from the formatter output. The <code>toString</code>
1903: * method of the given formatter should be overridden to provide a useful
1904: * subject, if possible.
1905: *
1906: * @param format the subject formatter or null for default formatter.
1907: * @throws SecurityException if a security manager exists and the
1908: * caller does not have <code>LoggingPermission("control")</code>.
1909: * @throws IllegalStateException if called from inside a push.
1910: * @see Character#isISOControl(char)
1911: * @see Character#isISOControl(int)
1912: */
1913: public final void setSubject(final Formatter format) {
1914: setSubjectFormatter(format);
1915: }
1916:
1917: /**
1918: * Sets the subject formatter for email. The format method of the subject
1919: * formatter will see all <code>LogRecord</code> objects that were published
1920: * to this <code>Handler</code> during formatting and will typically return
1921: * an empty string. This formatter is used to gather information to create
1922: * a summary about what information is contained in the email. The
1923: * <code>getTail</code> method should be used to construct the subject and
1924: * reset any formatter collected state. All control characters
1925: * will be removed from the formatter output. The <code>toString</code>
1926: * method of the given formatter should be overridden to provide a useful
1927: * subject, if possible.
1928: *
1929: * @param format the subject formatter or null for default formatter.
1930: * @throws SecurityException if a security manager exists and the
1931: * caller does not have <code>LoggingPermission("control")</code>.
1932: * @throws IllegalStateException if called from inside a push.
1933: * @see Character#isISOControl(char)
1934: * @see Character#isISOControl(int)
1935: * @since Angus Mail 2.0.3
1936: */
1937: public synchronized final void setSubjectFormatter(final Formatter format) {
1938: checkAccess();
1939: if (format != null) {
1940: if (isWriting) {
1941: throw new IllegalStateException();
1942: }
1943: this.subjectFormatter = format;
1944: } else {
1945: initSubject((String) null);
1946: }
1947: }
1948:
1949: /**
1950: * Protected convenience method to report an error to this Handler's
1951: * ErrorManager. This method will prefix all non null error messages with
1952: * <code>Level.SEVERE.getName()</code>. This allows the receiving error
1953: * manager to determine if the <code>msg</code> parameter is a simple error
1954: * message or a raw email message.
1955: *
1956: * @param msg a descriptive string (may be null)
1957: * @param ex an exception (may be null)
1958: * @param code an error code defined in ErrorManager
1959: */
1960: @Override
1961: protected void reportError(String msg, Exception ex, int code) {
1962: //This method is not protected from a this-escape.
1963: //The error manager will be non-null in any case.
1964: try {
1965: if (msg != null) {
1966: errorManager.error(Level.SEVERE.getName()
1967: .concat(": ").concat(msg), ex, code);
1968: } else {
1969: errorManager.error((String) null, ex, code);
1970: }
1971: } catch (final RuntimeException | LinkageError GLASSFISH_21258) {
1972: if (ex != null && GLASSFISH_21258 != ex) {
1973: GLASSFISH_21258.addSuppressed(ex);
1974: }
1975: reportLinkageError(GLASSFISH_21258, code);
1976: } catch (final ServiceConfigurationError sce) {
1977: if (ex != null) {
1978: sce.addSuppressed(ex);
1979: }
1980: reportConfigurationError(sce, code);
1981: }
1982: }
1983:
1984: /**
1985: * Checks logging permissions if this handler has been sealed.
1986: * Otherwise, this will check that this object was fully constructed.
1987: *
1988: * @throws SecurityException if a security manager exists and the caller
1989: * does not have {@code LoggingPermission("control")}.
1990: */
1991: private void checkAccess() {
1992: if (this.sealed) {
1993: LogManagerProperties.checkLogManagerAccess();
1994: } else {
1995: throw new SecurityException("this-escape");
1996: }
1997: }
1998:
1999: /**
2000: * Determines the mimeType of a formatter from the getHead call.
2001: * This could be made protected, or a new class could be created to do
2002: * this type of conversion. Currently, this is only used for the body
2003: * since the attachments are computed by filename.
2004: * Package-private for unit testing.
2005: *
2006: * @param chunk any char sequence or null.
2007: * @return return the mime type or null for text/plain.
2008: */
2009: final String contentTypeOf(CharSequence chunk) {
2010: if (!isEmpty(chunk)) {
2011: final int MAX_CHARS = 25;
2012: if (chunk.length() > MAX_CHARS) {
2013: chunk = chunk.subSequence(0, MAX_CHARS);
2014: }
2015: try {
2016: final String charset = getEncodingName();
2017: final byte[] b = chunk.toString().getBytes(charset);
2018: final ByteArrayInputStream in = new ByteArrayInputStream(b);
2019: assert in.markSupported() : in.getClass().getName();
2020: return URLConnection.guessContentTypeFromStream(in);
2021: } catch (final IOException IOE) {
2022: reportError("Unable to guess content type",
2023: IOE, ErrorManager.FORMAT_FAILURE);
2024: }
2025: }
2026: return null; //text/plain
2027: }
2028:
2029: /**
2030: * Determines the mimeType of a formatter by the class name. This method
2031: * avoids calling getHead and getTail of content formatters during verify
2032: * because they might trigger side effects or excessive work. The name
2033: * formatters and subject are usually safe to call.
2034: * Package-private for unit testing.
2035: *
2036: * @param f the formatter or null.
2037: * @return return the mime type or null, meaning text/plain.
2038: * @since JavaMail 1.5.6
2039: */
2040: final String contentTypeOf(final Formatter f) {
2041: assert Thread.holdsLock(this);
2042: if (f != null) {
2043: String type = getContentType(f.getClass().getName());
2044: if (type != null) {
2045: return type;
2046: }
2047:
2048: for (Class<?> k = f.getClass(); k != Formatter.class;
2049: k = k.getSuperclass()) {
2050: String name;
2051: try {
2052: name = k.getSimpleName();
2053: } catch (final InternalError JDK8057919) {
2054: name = k.getName();
2055: }
2056: name = name.toLowerCase(Locale.ENGLISH);
2057: for (int idx = name.indexOf('$') + 1;
2058: (idx = name.indexOf("ml", idx)) > -1; idx += 2) {
2059: if (idx > 0) {
2060: if (name.charAt(idx - 1) == 'x') {
2061: return "application/xml";
2062: }
2063: if (idx > 1 && name.charAt(idx - 2) == 'h'
2064: && name.charAt(idx - 1) == 't') {
2065: return "text/html";
2066: }
2067: }
2068: }
2069: }
2070: }
2071: return null;
2072: }
2073:
2074: /**
2075: * Determines if the given throwable is a no content exception. It is
2076: * assumed Transport.sendMessage will call Message.writeTo so we need to
2077: * ignore any exceptions that could be layered on top of that call chain to
2078: * infer that sendMessage is failing because of writeTo. Package-private
2079: * for unit testing.
2080: *
2081: * @param msg the message without content.
2082: * @param t the throwable chain to test.
2083: * @return true if the throwable is a missing content exception.
2084: * @throws NullPointerException if any of the arguments are null.
2085: * @since JavaMail 1.4.5
2086: */
2087: @SuppressWarnings({"UseSpecificCatch", "ThrowableResultIgnored"})
2088: final boolean isMissingContent(Message msg, Throwable t) {
2089: final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
2090: try {
2091: msg.writeTo(new ByteArrayOutputStream(MIN_HEADER_SIZE));
2092: } catch (final RuntimeException RE) {
2093: throw RE; //Avoid catch all.
2094: } catch (final Exception noContent) {
2095: final String txt = noContent.getMessage();
2096: if (!isEmpty(txt)) {
2097: int limit = 0;
2098: while (t != null) {
2099: if (noContent.getClass() == t.getClass()
2100: && txt.equals(t.getMessage())) {
2101: return true;
2102: }
2103:
2104: //Not all Jakarta Mail implementations support JDK 1.4
2105: //exception chaining.
2106: final Throwable cause = t.getCause();
2107: if (cause == null && t instanceof MessagingException) {
2108: t = ((MessagingException) t).getNextException();
2109: } else {
2110: t = cause;
2111: }
2112:
2113: //Deal with excessive cause chains and cyclic throwables.
2114: if (++limit == (1 << 16)) {
2115: break; //Give up.
2116: }
2117: }
2118: }
2119: } finally {
2120: getAndSetContextClassLoader(ccl);
2121: }
2122: return false;
2123: }
2124:
2125: /**
2126: * Converts a mime message to a raw string or formats the reason
2127: * why message can't be changed to raw string and reports it.
2128: *
2129: * @param msg the mime message.
2130: * @param ex the original exception.
2131: * @param code the ErrorManager code.
2132: * @since JavaMail 1.4.5
2133: */
2134: @SuppressWarnings("UseSpecificCatch")
2135: private void reportError(Message msg, Exception ex, int code) {
2136: try {
2137: try { //Use direct call so we do not prefix raw email.
2138: errorManager.error(toRawString(msg), ex, code);
2139: } catch (final Exception e) {
2140: reportError(toMsgString(e), ex, code);
2141: }
2142: } catch (LinkageError GLASSFISH_21258) {
2143: if (ex != null) {
2144: GLASSFISH_21258.addSuppressed(ex);
2145: }
2146: reportLinkageError(GLASSFISH_21258, code);
2147: } catch (ServiceConfigurationError sce) {
2148: if (ex != null) {
2149: sce.addSuppressed(ex);
2150: }
2151: reportConfigurationError(sce, code);
2152: }
2153: }
2154:
2155: /**
2156: * Report a ServiceConfigurationError to the error manager.
2157: *
2158: * @param t the service configuration error.
2159: * @param code the error manager reason code.
2160: * @since Angus Mail 2.0.3
2161: */
2162: private void reportConfigurationError(Throwable t, int code) {
2163: final Integer idx = MUTEX.get();
2164: if (idx == null || idx > MUTEX_SERVICE) {
2165: MUTEX.set(MUTEX_SERVICE);
2166: try {
2167: reportError("Unable to load dependencies",
2168: new IllegalStateException(t), code);
2169: } catch (RuntimeException | ServiceConfigurationError
2170: | LinkageError e) {
2171: if (t != null && e != t) {
2172: e.addSuppressed(t);
2173: }
2174: reportLinkageError(e, code);
2175: } finally {
2176: if (idx != null) {
2177: MUTEX.set(idx);
2178: } else {
2179: MUTEX.remove();
2180: }
2181: }
2182: }
2183: }
2184:
2185: /**
2186: * Reports the given linkage error or runtime exception.
2187: *
2188: * The current LogManager code will stop closing all remaining handlers if
2189: * an error is thrown during resetLogger. This is a workaround for
2190: * GLASSFISH-21258 and JDK-8152515.
2191: *
2192: * @param le the linkage error or a RuntimeException.
2193: * @param code the ErrorManager code.
2194: * @since JavaMail 1.5.3
2195: */
2196: private void reportLinkageError(final Throwable le, final int code) {
2197: assert le != null : code;
2198: final Integer idx = MUTEX.get();
2199: if (idx == null || idx > MUTEX_LINKAGE) {
2200: MUTEX.set(MUTEX_LINKAGE);
2201: try {
2202: //Per the API docs this is not how uncaught exception handler
2203: //should be used. However, the throwable that we are receiving
2204: //here is happening only when the JVM is shutting down.
2205: //This will only execute on unpatched systems.
2206: //See tickets listed in API docs above.
2207: Thread.currentThread().getUncaughtExceptionHandler()
2208: .uncaughtException(Thread.currentThread(), le);
2209: } catch (RuntimeException | ServiceConfigurationError
2210: | LinkageError ignore) {
2211: } finally {
2212: if (idx != null) {
2213: MUTEX.set(idx);
2214: } else {
2215: MUTEX.remove();
2216: }
2217: }
2218: }
2219: }
2220:
2221: /**
2222: * Determines the mimeType from the given file name.
2223: * Used to override the body content type and used for all attachments.
2224: *
2225: * @param name the file name or class name.
2226: * @return the mime type or null for text/plain.
2227: */
2228: private String getContentType(final String name) {
2229: assert Thread.holdsLock(this);
2230: if (contentTypes == null) {
2231: return null;
2232: }
2233:
2234: final String type = contentTypes.getContentType(name);
2235: if ("application/octet-stream".equalsIgnoreCase(type)) {
2236: return null; //Formatters return strings, default to text/plain.
2237: }
2238: return type;
2239: }
2240:
2241: /**
2242: * Gets the encoding set for this handler, mime encoding, or file encoding.
2243: *
2244: * @return the java charset name, never null.
2245: * @since JavaMail 1.4.5
2246: */
2247: private String getEncodingName() {
2248: String charset = getEncoding();
2249: if (charset == null) {
2250: charset = MimeUtility.getDefaultJavaCharset();
2251: }
2252: return charset;
2253: }
2254:
2255: /**
2256: * Set the content for a part using the encoding assigned to the handler.
2257: *
2258: * @param part the part to assign.
2259: * @param buf the formatted data.
2260: * @param type the mime type or null, meaning text/plain.
2261: * @throws MessagingException if there is a problem.
2262: */
2263: private void setContent(MimePart part, CharSequence buf, String type) throws MessagingException {
2264: final String charset = getEncodingName();
2265: if (type != null && !"text/plain".equalsIgnoreCase(type)) {
2266: type = contentWithEncoding(type, charset);
2267: try {
2268: DataSource source = new ByteArrayDataSource(buf.toString(), type);
2269: part.setDataHandler(new DataHandler(source));
2270: } catch (final IOException IOE) {
2271: reportError(IOE.getMessage(), IOE, ErrorManager.FORMAT_FAILURE);
2272: part.setText(buf.toString(), charset);
2273: }
2274: } else {
2275: part.setText(buf.toString(), MimeUtility.mimeCharset(charset));
2276: }
2277: }
2278:
2279: /**
2280: * Replaces the charset parameter with the current encoding.
2281: *
2282: * @param type the content type.
2283: * @param encoding the java charset name.
2284: * @return the type with a specified encoding.
2285: */
2286: private String contentWithEncoding(String type, String encoding) {
2287: assert encoding != null;
2288: try {
2289: final ContentType ct = new ContentType(type);
2290: ct.setParameter("charset", MimeUtility.mimeCharset(encoding));
2291: encoding = ct.toString(); //See jakarta.mail.internet.ContentType.
2292: if (!isEmpty(encoding)) { //Support pre K5687.
2293: type = encoding;
2294: }
2295: } catch (final MessagingException ME) {
2296: reportError(type, ME, ErrorManager.FORMAT_FAILURE);
2297: }
2298: return type;
2299: }
2300:
2301: /**
2302: * Sets the capacity for this handler.
2303: *
2304: * @param newCapacity the max number of records. Default capacity is used if
2305: * the value is negative.
2306: * @throws IllegalStateException if called from inside a push.
2307: */
2308: private synchronized void setCapacity0(int newCapacity) {
2309: if (isWriting) {
2310: throw new IllegalStateException();
2311: }
2312:
2313: if (!this.sealed || this.capacity == 0) {
2314: return;
2315: }
2316:
2317: if (newCapacity <= 0) {
2318: newCapacity = DEFAULT_CAPACITY;
2319: }
2320:
2321: if (this.capacity < 0) { //If closed, remain closed.
2322: this.capacity = -newCapacity;
2323: } else {
2324: push(false, ErrorManager.FLUSH_FAILURE);
2325: this.capacity = newCapacity;
2326: if (this.data.length > newCapacity) {
2327: initLogRecords(1);
2328: }
2329: }
2330: }
2331:
2332: /**
2333: * Gets the attachment filters using a happens-before relationship between
2334: * this method and setAttachmentFilters. The attachment filters are treated
2335: * as copy-on-write, so the returned array must never be modified or
2336: * published outside this class.
2337: *
2338: * @return a read only array of filters.
2339: */
2340: private Filter[] readOnlyAttachmentFilters() {
2341: return this.attachmentFilters;
2342: }
2343:
2344: /**
2345: * Factory for empty formatter arrays.
2346: *
2347: * @return an empty array.
2348: */
2349: private static Formatter[] emptyFormatterArray() {
2350: return EMPTY_FORMATTERS;
2351: }
2352:
2353: /**
2354: * Factory for empty filter arrays.
2355: *
2356: * @return an empty array.
2357: */
2358: private static Filter[] emptyFilterArray() {
2359: return EMPTY_FILTERS;
2360: }
2361:
2362: /**
2363: * Expand or shrink the attachment name formatters with the attachment
2364: * formatters.
2365: *
2366: * @return true if size was changed.
2367: */
2368: private boolean alignAttachmentNames(int expect) {
2369: assert Thread.holdsLock(this);
2370: boolean fixed = false;
2371: final int current = this.attachmentNames.length;
2372: if (current != expect) {
2373: this.attachmentNames = Arrays.copyOf(attachmentNames, expect,
2374: Formatter[].class);
2375: fixed = current != 0;
2376: }
2377:
2378: //Copy of zero length array is cheap, warm up copyOf.
2379: if (expect == 0) {
2380: this.attachmentNames = emptyFormatterArray();
2381: assert this.attachmentNames.length == 0;
2382: } else {
2383: for (int i = 0; i < expect; ++i) {
2384: if (this.attachmentNames[i] == null) {
2385: this.attachmentNames[i] = TailNameFormatter.of(
2386: toString(this.attachmentFormatters[i]));
2387: }
2388: }
2389: }
2390: return fixed;
2391: }
2392:
2393: private boolean alignAttachmentFormatters(int expect) {
2394: assert Thread.holdsLock(this);
2395: boolean fixed = false;
2396: final int current = this.attachmentFormatters.length;
2397: if (current != expect) {
2398: this.attachmentFormatters = Arrays.copyOf(attachmentFormatters, expect,
2399: Formatter[].class);
2400: fixed = current != 0;
2401: }
2402:
2403: //Copy of zero length array is cheap, warm up copyOf.
2404: if (expect == 0) {
2405: this.attachmentFormatters = emptyFormatterArray();
2406: assert this.attachmentFormatters.length == 0;
2407: } else {
2408: for (int i = current; i < expect; ++i) {
2409: if (this.attachmentFormatters[i] == null) {
2410: this.attachmentFormatters[i] = createSimpleFormatter();
2411: }
2412: }
2413: }
2414: return fixed;
2415: }
2416:
2417: /**
2418: * Expand or shrink the attachment filters with the attachment formatters.
2419: *
2420: * @return true if the size was changed.
2421: */
2422: private boolean alignAttachmentFilters(int expect) {
2423: assert Thread.holdsLock(this);
2424:
2425: boolean fixed = false;
2426: final int current = this.attachmentFilters.length;
2427: if (current != expect) {
2428: this.attachmentFilters = Arrays.copyOf(attachmentFilters, expect,
2429: Filter[].class);
2430: clearMatches(Math.min(current, expect));
2431: fixed = current != 0;
2432:
2433: //Array elements default to null so skip filling if body filter
2434: //is null. If not null then only assign to expanded elements.
2435: final Filter body = this.filter;
2436: if (body != null) {
2437: for (int i = current; i < expect; ++i) {
2438: this.attachmentFilters[i] = body;
2439: }
2440: }
2441: }
2442:
2443: //Copy of zero length array is cheap, warm up copyOf.
2444: if (expect == 0) {
2445: this.attachmentFilters = emptyFilterArray();
2446: assert this.attachmentFilters.length == 0;
2447: }
2448: return fixed;
2449: }
2450:
2451: /**
2452: * Sets the size to zero and clears the current buffer.
2453: */
2454: private void reset() {
2455: assert Thread.holdsLock(this);
2456: if (size < data.length) {
2457: Arrays.fill(data, 0, size, (LogRecord) null);
2458: } else {
2459: Arrays.fill(data, (LogRecord) null);
2460: }
2461: this.size = 0;
2462: }
2463:
2464: /**
2465: * Expands the internal buffer up to the capacity.
2466: */
2467: private void grow() {
2468: assert Thread.holdsLock(this);
2469: final int len = data.length;
2470: int newCapacity = len + (len >> 1) + 1;
2471: if (newCapacity > capacity || newCapacity < len) {
2472: newCapacity = capacity;
2473: }
2474: assert len != capacity : len;
2475: final LogRecord[] d = Arrays.copyOf(data, newCapacity, LogRecord[].class);
2476: final int[] m = Arrays.copyOf(matched, newCapacity);
2477: //Ensure both arrays are created before assigning.
2478: this.data = d;
2479: this.matched = m;
2480: }
2481:
2482: /**
2483: * Configures the handler properties from the log manager. On normal return
2484: * this object will be sealed.
2485: *
2486: * @param props the given mail properties. Maybe null and are never
2487: * captured by this handler.
2488: * @throws SecurityException if a security manager exists and the
2489: * caller does not have <code>LoggingPermission("control")</code>.
2490: * @see #sealed
2491: */
2492: private synchronized void init(final Properties props) {
2493: //Ensure non-null even on exception.
2494: //Zero value allows publish to not check for this-escape.
2495: initLogRecords(0);
2496: LogManagerProperties.checkLogManagerAccess();
2497:
2498: final String p = getClass().getName();
2499: //Assign any custom error manager first so it can detect all failures.
2500: assert this.errorManager != null; //default set before custom object
2501: initErrorManager(fromLogManager(p.concat(".errorManager")));
2502: int cap = parseCapacity(fromLogManager(p.concat(".capacity")));
2503: Level lvl = parseLevel(fromLogManager(p.concat(".level")));
2504: boolean enabled = parseEnabled(fromLogManager(p.concat(".enabled")));
2505: initContentTypes();
2506:
2507: initFilter(fromLogManager(p.concat(".filter")));
2508: this.auth = newAuthenticator(fromLogManager(p.concat(".authenticator")));
2509:
2510: initEncoding(fromLogManager(p.concat(".encoding")));
2511: initFormatter(fromLogManager(p.concat(".formatter")));
2512: initComparator(fromLogManager(p.concat(".comparator")));
2513: initComparatorReverse(fromLogManager(p.concat(".comparator.reverse")));
2514: initPushLevel(fromLogManager(p.concat(".pushLevel")));
2515: initPushFilter(fromLogManager(p.concat(".pushFilter")));
2516:
2517: initSubject(fromLogManager(p.concat(".subject")));
2518:
2519: initAttachmentFormaters(fromLogManager(p.concat(".attachment.formatters")));
2520: initAttachmentFilters(fromLogManager(p.concat(".attachment.filters")));
2521: initAttachmentNames(fromLogManager(p.concat(".attachment.names")));
2522:
2523: //Entries are always parsed to report any errors.
2524: Properties entries = parseProperties(fromLogManager(p.concat(".mailEntries")));
2525:
2526: //Any new handler object members should be set above this line
2527: String verify = fromLogManager(p.concat(".verify"));
2528: boolean verified;
2529: if (props != null) {
2530: //Given properties do not fallback to log manager.
2531: setMailProperties0(copyOf(props));
2532: verified = true;
2533: } else if (entries != null) {
2534: //.mailEntries should fallback to log manager when verify key not present.
2535: verified = setMailProperties0(entries);
2536: } else {
2537: verified = false;
2538: }
2539:
2540: //Fallback to top level verify properties if needed.
2541: if (!verified && verify != null) {
2542: try {
2543: verifySettings(initSession());
2544: } catch (final RuntimeException re) {
2545: reportError("Unable to verify", re, ErrorManager.OPEN_FAILURE);
2546: } catch (final ServiceConfigurationError sce) {
2547: reportConfigurationError(sce, ErrorManager.OPEN_FAILURE);
2548: }
2549: }
2550: intern(); //Show verify warnings first.
2551:
2552: //Mark the handler as fully constructed by setting these fields.
2553: this.capacity = cap;
2554: if (enabled) {
2555: this.logLevel = lvl;
2556: } else {
2557: this.disabledLevel = lvl;
2558: }
2559: sealed = true;
2560: }
2561:
2562: /**
2563: * Interns the error manager, formatters, and filters contained in this
2564: * handler. The comparator is not interned. This method can only be
2565: * called from init after all of formatters and filters are in a constructed
2566: * and in a consistent state.
2567: *
2568: * @since JavaMail 1.5.0
2569: */
2570: private void intern() {
2571: assert Thread.holdsLock(this);
2572: try {
2573: Object canidate;
2574: Object result;
2575: final Map<Object, Object> seen = new HashMap<>();
2576: intern(seen, this.errorManager);
2577:
2578: canidate = this.filter;
2579: result = intern(seen, canidate);
2580: if (result != canidate && result instanceof Filter) {
2581: this.filter = (Filter) result;
2582: }
2583:
2584: canidate = this.formatter;
2585: result = intern(seen, canidate);
2586: if (result != canidate && result instanceof Formatter) {
2587: this.formatter = (Formatter) result;
2588: }
2589:
2590: canidate = this.subjectFormatter;
2591: result = intern(seen, canidate);
2592: if (result != canidate && result instanceof Formatter) {
2593: this.subjectFormatter = (Formatter) result;
2594: }
2595:
2596: canidate = this.pushFilter;
2597: result = intern(seen, canidate);
2598: if (result != canidate && result instanceof Filter) {
2599: this.pushFilter = (Filter) result;
2600: }
2601:
2602: for (int i = 0; i < attachmentFormatters.length; ++i) {
2603: canidate = attachmentFormatters[i];
2604: result = intern(seen, canidate);
2605: if (result != canidate && result instanceof Formatter) {
2606: attachmentFormatters[i] = (Formatter) result;
2607: }
2608:
2609: canidate = attachmentFilters[i];
2610: result = intern(seen, canidate);
2611: if (result != canidate && result instanceof Filter) {
2612: attachmentFilters[i] = (Filter) result;
2613: }
2614:
2615: canidate = attachmentNames[i];
2616: result = intern(seen, canidate);
2617: if (result != canidate && result instanceof Formatter) {
2618: attachmentNames[i] = (Formatter) result;
2619: }
2620: }
2621: } catch (final Exception skip) {
2622: reportError("Unable to deduplcate", skip, ErrorManager.OPEN_FAILURE);
2623: } catch (final LinkageError skip) {
2624: reportError("Unable to deduplcate", new InvocationTargetException(skip),
2625: ErrorManager.OPEN_FAILURE);
2626: } catch (final ServiceConfigurationError skip) {
2627: reportConfigurationError(skip, ErrorManager.OPEN_FAILURE);
2628: }
2629: }
2630:
2631: /**
2632: * If possible performs an intern of the given object into the
2633: * map. If the object can not be interned the given object is returned.
2634: *
2635: * @param m the map used to record the interned values.
2636: * @param o the object to try an intern.
2637: * @return the original object or an intern replacement.
2638: * @throws SecurityException if this operation is not allowed by the
2639: * security manager.
2640: * @throws Exception if there is an unexpected problem.
2641: * @since JavaMail 1.5.0
2642: */
2643: private Object intern(Map<Object, Object> m, Object o) throws Exception {
2644: if (o == null) {
2645: return null;
2646: }
2647:
2648: /**
2649: * The common case is that most objects will not intern. The given
2650: * object has a public no argument constructor or is an instance of a
2651: * TailNameFormatter. TailNameFormatter is safe use as a map key.
2652: * For everything else we create a clone of the given object.
2653: * This is done because of the following:
2654: * 1. Clones can be used to test that a class provides an equals method
2655: * and that the equals method works correctly.
2656: * 2. Calling equals on the given object is assumed to be cheap.
2657: * 3. The intern map can be filtered so it only contains objects that
2658: * can be interned, which reduces the memory footprint.
2659: * 4. Clones are method local garbage.
2660: * 5. Hash code is only called on the clones so bias locking is not
2661: * disabled on the objects the handler will use.
2662: */
2663: final Object key;
2664: if (o.getClass().getName().equals(TailNameFormatter.class.getName())) {
2665: key = o;
2666: } else {
2667: //This call was already made in the LogManagerProperties so this
2668: //shouldn't trigger loading of any lazy reflection code.
2669: key = o.getClass().getConstructor().newInstance();
2670: }
2671:
2672: final Object use;
2673: //Check the classloaders of each object avoiding the security manager.
2674: if (key.getClass() == o.getClass()) {
2675: Object found = m.get(key); //Transitive equals test.
2676: if (found == null) {
2677: //Ensure that equals is symmetric to prove intern is safe.
2678: final boolean right = key.equals(o);
2679: final boolean left = o.equals(key);
2680: if (right && left) {
2681: //Assume hashCode is defined at this point.
2682: found = m.put(o, o);
2683: if (found != null) {
2684: reportNonDiscriminating(key, found);
2685: found = m.remove(key);
2686: if (found != o) {
2687: reportNonDiscriminating(key, found);
2688: m.clear(); //Try to restore order.
2689: }
2690: }
2691: } else {
2692: if (right != left) {
2693: reportNonSymmetric(o, key);
2694: }
2695: }
2696: use = o;
2697: } else {
2698: //Check for a discriminating equals method.
2699: if (o.getClass() == found.getClass()) {
2700: use = found;
2701: } else {
2702: reportNonDiscriminating(o, found);
2703: use = o;
2704: }
2705: }
2706: } else {
2707: use = o;
2708: }
2709: return use;
2710: }
2711:
2712: /**
2713: * Factory method used to create a java.util.logging.SimpleFormatter.
2714: *
2715: * @return a new SimpleFormatter.
2716: * @since JavaMail 1.5.6
2717: */
2718: private static Formatter createSimpleFormatter() {
2719: //Don't force the byte code verifier to load the formatter.
2720: return Formatter.class.cast(new SimpleFormatter());
2721: }
2722:
2723: /**
2724: * Checks a char sequence value for null or empty.
2725: *
2726: * @param s the char sequence.
2727: * @return true if the given string is null or zero length.
2728: */
2729: private static boolean isEmpty(final CharSequence s) {
2730: return s == null || s.length() == 0;
2731: }
2732:
2733: /**
2734: * Checks that a string is not empty and not equal to the literal "null".
2735: *
2736: * @param name the string to check for a value.
2737: * @return true if the string has a valid value.
2738: */
2739: private static boolean hasValue(final String name) {
2740: return !isEmpty(name) && !"null".equalsIgnoreCase(name);
2741: }
2742:
2743: /**
2744: * Parses LogManager string values into objects used by this handler.
2745: *
2746: * @param list the list of attachment filter class names.
2747: * @throws SecurityException if not allowed.
2748: */
2749: private void initAttachmentFilters(final String list) {
2750: assert Thread.holdsLock(this);
2751: assert this.attachmentFormatters != null;
2752: if (!isEmpty(list)) {
2753: final String[] names = list.split(",");
2754: Filter[] a = new Filter[names.length];
2755: for (int i = 0; i < a.length; ++i) {
2756: names[i] = names[i].trim();
2757: if (!"null".equalsIgnoreCase(names[i])) {
2758: try {
2759: a[i] = LogManagerProperties.newFilter(names[i]);
2760: } catch (final SecurityException SE) {
2761: throw SE; //Avoid catch all.
2762: } catch (final Exception E) {
2763: reportError(Integer.toString(i), E, ErrorManager.OPEN_FAILURE);
2764: }
2765: }
2766: }
2767:
2768: this.attachmentFilters = a;
2769: if (alignAttachmentFilters(attachmentFormatters.length)) {
2770: reportError("Attachment filters.",
2771: attachmentMismatch("Length mismatch."), ErrorManager.OPEN_FAILURE);
2772: }
2773: } else {
2774: this.attachmentFilters = emptyFilterArray();
2775: alignAttachmentFilters(attachmentFormatters.length);
2776: }
2777: }
2778:
2779: /**
2780: * Parses LogManager string values into objects used by this handler.
2781: *
2782: * @param list the list of attachment formatter class names or literal names.
2783: * @throws SecurityException if not allowed.
2784: */
2785: private void initAttachmentFormaters(final String list) {
2786: assert Thread.holdsLock(this);
2787: if (!isEmpty(list)) {
2788: final Formatter[] a;
2789: final String[] names = list.split(",");
2790: if (names.length == 0) {
2791: a = emptyFormatterArray();
2792: } else {
2793: a = new Formatter[names.length];
2794: }
2795:
2796: for (int i = 0; i < a.length; ++i) {
2797: names[i] = names[i].trim();
2798: if (!"null".equalsIgnoreCase(names[i])) {
2799: try {
2800: a[i] = LogManagerProperties.newFormatter(names[i]);
2801: if (a[i] instanceof TailNameFormatter) {
2802: final Exception CNFE = new ClassNotFoundException(a[i].toString());
2803: reportError("Attachment formatter.", CNFE, ErrorManager.OPEN_FAILURE);
2804: a[i] = createSimpleFormatter();
2805: }
2806: } catch (final SecurityException SE) {
2807: throw SE; //Avoid catch all.
2808: } catch (final Exception E) {
2809: reportError(Integer.toString(i), E, ErrorManager.OPEN_FAILURE);
2810: a[i] = createSimpleFormatter();
2811: }
2812: } else {
2813: a[i] = createSimpleFormatter();
2814: }
2815: }
2816:
2817: this.attachmentFormatters = a;
2818: } else {
2819: this.attachmentFormatters = emptyFormatterArray();
2820: }
2821: }
2822:
2823: /**
2824: * Parses LogManager string values into objects used by this handler.
2825: *
2826: * @param list of formatter class names or literals.
2827: * @throws SecurityException if not allowed.
2828: */
2829: private void initAttachmentNames(final String list) {
2830: assert Thread.holdsLock(this);
2831: assert this.attachmentFormatters != null;
2832:
2833: if (!isEmpty(list)) {
2834: final String[] names = list.split(",");
2835: final Formatter[] a = new Formatter[names.length];
2836: for (int i = 0; i < a.length; ++i) {
2837: names[i] = names[i].trim();
2838: if (!"null".equalsIgnoreCase(names[i])) {
2839: try {
2840: try {
2841: a[i] = LogManagerProperties.newFormatter(names[i]);
2842: } catch (ClassNotFoundException
2843: | ClassCastException literal) {
2844: a[i] = TailNameFormatter.of(names[i]);
2845: }
2846: } catch (final SecurityException SE) {
2847: throw SE; //Avoid catch all.
2848: } catch (final Exception E) {
2849: reportError(Integer.toString(i), E, ErrorManager.OPEN_FAILURE);
2850: }
2851: } else {
2852: a[i] = TailNameFormatter.of(toString(attachmentFormatters[i]));
2853: }
2854: }
2855:
2856: this.attachmentNames = a;
2857: if (alignAttachmentNames(attachmentFormatters.length)) {
2858: reportError("Attachment names.",
2859: attachmentMismatch("Length mismatch."), ErrorManager.OPEN_FAILURE);
2860: }
2861: } else {
2862: this.attachmentNames = emptyFormatterArray();
2863: alignAttachmentNames(attachmentFormatters.length);
2864: }
2865: }
2866:
2867: /**
2868: * Parses LogManager string values into objects used by this handler.
2869: *
2870: * @param name the authenticator class name, literal password, or empty string.
2871: * @throws SecurityException if not allowed.
2872: */
2873: private Authenticator newAuthenticator(final String name) {
2874: Authenticator a = null;
2875: if (name != null && !"null".equalsIgnoreCase(name)) {
2876: if (!name.isEmpty()) {
2877: try {
2878: a = LogManagerProperties
2879: .newObjectFrom(name, Authenticator.class);
2880: } catch (final SecurityException SE) {
2881: throw SE;
2882: } catch (final ClassNotFoundException
2883: | ClassCastException literalAuth) {
2884: a = DefaultAuthenticator.of(name);
2885: } catch (final Exception E) {
2886: reportError("Unable to create authenticator",
2887: E, ErrorManager.OPEN_FAILURE);
2888: } catch (final LinkageError GLASSFISH_21258) {
2889: reportLinkageError(GLASSFISH_21258,
2890: ErrorManager.OPEN_FAILURE);
2891: }
2892: } else { //Authenticator is installed to provide the user name.
2893: a = DefaultAuthenticator.of(name);
2894: }
2895: }
2896: return a;
2897: }
2898:
2899: /**
2900: * Parses LogManager string values into objects used by this handler.
2901: *
2902: * @param nameOrNumber the level name or number.
2903: * @throws SecurityException if not allowed.
2904: */
2905: private Level parseLevel(final String nameOrNumber) {
2906: assert Thread.holdsLock(this);
2907: assert disabledLevel == null : disabledLevel;
2908: Level lvl = Level.WARNING;
2909: try {
2910: if (!isEmpty(nameOrNumber)) {
2911: lvl = Level.parse(nameOrNumber);
2912: }
2913: } catch (final SecurityException SE) {
2914: throw SE; //Avoid catch all.
2915: } catch (final RuntimeException RE) {
2916: reportError(nameOrNumber, RE, ErrorManager.OPEN_FAILURE);
2917: }
2918: return lvl;
2919: }
2920:
2921: /**
2922: * Creates the internal collection to store log records.
2923: * This method assumes that no log records are currently stored.
2924: *
2925: * @param records the number of records.
2926: * @throws RuntimeException if records is negative.
2927: * @since Angus Mail 2.0.3
2928: */
2929: private void initLogRecords(final int records) {
2930: assert this.size == 0 : this.size;
2931: final LogRecord[] d = new LogRecord[records];
2932: final int[] m = new int[records];
2933: //Ensure both arrays are created before assigning.
2934: this.data = d;
2935: this.matched = m;
2936: }
2937:
2938: /**
2939: * Parses the given properties lines. Any parsing errors are reported to the
2940: * error manager.
2941: *
2942: * @param entries one or more key/value pairs. An empty string, null value
2943: * or, the literal null are all treated as empty properties and will simply
2944: * clear all existing mail properties assigned to this handler.
2945: * @return the parsed properties or null if entries was null.
2946: * @since Angus Mail 2.0.3
2947: * @see #setMailEntries(java.lang.String)
2948: */
2949: private Properties parseProperties(String entries) {
2950: if (entries == null) {
2951: return null;
2952: }
2953:
2954: final Properties props = new Properties();
2955: if (!hasValue(entries)) {
2956: return props;
2957: }
2958:
2959: /**
2960: * The characters # and ! are used for comment lines in properties
2961: * format. The characters \r or \n are not allowed in WildFly form
2962: * validation however, properties comment characters are allowed.
2963: * Comment lines are useless for this handler therefore, "#!"
2964: * characters are used to represent logical lines and are assumed to
2965: * not be present together in a key or value.
2966: */
2967: try {
2968: entries = entries.replace("#!", "\r\n");
2969: //Dynamic cast used so byte code verifier doesn't load StringReader
2970: props.load(Reader.class.cast(new StringReader(entries)));
2971: } catch (IOException | RuntimeException ex) {
2972: reportError(entries, ex, ErrorManager.OPEN_FAILURE);
2973: //Allow a partial load of properties to be set
2974: }
2975: return props;
2976: }
2977:
2978: /**
2979: * Disables this handler if the property was specified in LogManager.
2980: * Assumes that initLevel was called before this method.
2981: *
2982: * @param enabled the string false will only disable this handler.
2983: * @since Angus Mail 2.0.3
2984: */
2985: private boolean parseEnabled(final String enabled) {
2986: assert Thread.holdsLock(this);
2987: //By default the Handler is enabled so only need to disable it on init.
2988: return !hasValue(enabled) || Boolean.parseBoolean(enabled);
2989: }
2990:
2991: /**
2992: * Parses LogManager string values into objects used by this handler.
2993: *
2994: * @param name the filter class name or null.
2995: * @throws SecurityException if not allowed.
2996: */
2997: private void initFilter(final String name) {
2998: assert Thread.holdsLock(this);
2999: try {
3000: if (hasValue(name)) {
3001: filter = LogManagerProperties.newFilter(name);
3002: } else {
3003: filter = null;
3004: }
3005: } catch (final SecurityException SE) {
3006: throw SE; //Avoid catch all.
3007: } catch (final Exception E) {
3008: reportError("Unable to create filter",
3009: E, ErrorManager.OPEN_FAILURE);
3010: }
3011: }
3012:
3013: /**
3014: * Parses LogManager string values into objects used by this handler.
3015: *
3016: * @param value the capacity value.
3017: * @throws SecurityException if not allowed.
3018: */
3019: private int parseCapacity(final String value) {
3020: assert Thread.holdsLock(this);
3021: int cap = 0;
3022: try {
3023: if (value != null) {
3024: cap = Integer.parseInt(value);
3025: }
3026: } catch (final SecurityException SE) {
3027: throw SE; //Avoid catch all.
3028: } catch (final RuntimeException RE) {
3029: reportError("Unable to set capacity", RE, ErrorManager.OPEN_FAILURE);
3030: }
3031:
3032: if (cap <= 0) {
3033: cap = DEFAULT_CAPACITY;
3034: }
3035: return cap;
3036: }
3037:
3038: /**
3039: * Sets the encoding of this handler.
3040: *
3041: * @param e the encoding name or null.
3042: * @throws SecurityException if not allowed.
3043: */
3044: private void initEncoding(final String e) {
3045: assert Thread.holdsLock(this);
3046: try {
3047: setEncoding0(e);
3048: } catch (final SecurityException SE) {
3049: throw SE; //Avoid catch all.
3050: } catch (UnsupportedEncodingException | RuntimeException UEE) {
3051: reportError(e, UEE, ErrorManager.OPEN_FAILURE);
3052: }
3053: }
3054:
3055: /**
3056: * Used to get or create the default ErrorManager used before init.
3057: *
3058: * @return the super error manager or a new ErrorManager.
3059: * @since JavaMail 1.5.3
3060: */
3061: private ErrorManager defaultErrorManager() {
3062: ErrorManager em;
3063: try { //Try to share the super error manager.
3064: em = super.getErrorManager();
3065: } catch (RuntimeException | LinkageError ignore) {
3066: em = null;
3067: }
3068:
3069: //Don't assume that the super call is not null.
3070: if (em == null) {
3071: em = new ErrorManager();
3072: }
3073: return em;
3074: }
3075:
3076: /**
3077: * Creates the error manager for this handler.
3078: *
3079: * @param name the error manager class name.
3080: * @throws SecurityException if not allowed.
3081: */
3082: @SuppressWarnings("this-escape")
3083: private void initErrorManager(final String name) {
3084: try {
3085: if (name != null) {
3086: setErrorManager0(LogManagerProperties.newErrorManager(name));
3087: }
3088: } catch (final SecurityException SE) {
3089: throw SE; //Avoid catch all.
3090: } catch (final Exception E) {
3091: reportError("Unable to create error manager",
3092: E, ErrorManager.OPEN_FAILURE);
3093: }
3094: }
3095:
3096: /**
3097: * Parses LogManager string values into objects used by this handler.
3098: *
3099: * @param name the formatter class name or null.
3100: * @throws SecurityException if not allowed.
3101: */
3102: private void initFormatter(final String name) {
3103: assert Thread.holdsLock(this);
3104: try {
3105: if (hasValue(name)) {
3106: final Formatter f
3107: = LogManagerProperties.newFormatter(name);
3108: if (f instanceof TailNameFormatter == false) {
3109: formatter = f;
3110: } else {
3111: formatter = createSimpleFormatter();
3112: }
3113: } else {
3114: formatter = createSimpleFormatter();
3115: }
3116: } catch (final SecurityException SE) {
3117: throw SE; //Avoid catch all.
3118: } catch (final Exception E) {
3119: reportError("Unable to create formatter",
3120: E, ErrorManager.OPEN_FAILURE);
3121: formatter = createSimpleFormatter();
3122: }
3123: }
3124:
3125: /**
3126: * Creates the comparator for this handler.
3127: *
3128: * @param p the handler class name used as the prefix.
3129: * @throws SecurityException if not allowed.
3130: */
3131: private void initComparator(final String name) {
3132: assert Thread.holdsLock(this);
3133: try {
3134: if (hasValue(name)) {
3135: comparator = LogManagerProperties.newComparator(name);
3136: } else {
3137: comparator = null;
3138: }
3139: } catch (final SecurityException SE) {
3140: throw SE; //Avoid catch all.
3141: } catch (final Exception E) {
3142: reportError("Unable to create comparator", E, ErrorManager.OPEN_FAILURE);
3143: }
3144: }
3145:
3146:
3147: private void initComparatorReverse(final String reverse) {
3148: if (Boolean.parseBoolean(reverse)) {
3149: if (comparator != null) {
3150: comparator = LogManagerProperties.reverseOrder(comparator);
3151: } else {
3152: IllegalArgumentException E = new IllegalArgumentException(
3153: "No comparator to reverse.");
3154: reportError(reverse, E, ErrorManager.OPEN_FAILURE);
3155: }
3156: }
3157: }
3158:
3159: /**
3160: * Gets and assigns the content types for this handler.
3161: *
3162: * @since Angus Mail 2.0.3
3163: */
3164: private void initContentTypes() {
3165: try {
3166: Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
3167: try {
3168: this.contentTypes = FileTypeMap.getDefaultFileTypeMap();
3169: } finally { //reset ccl before reporting errors
3170: getAndSetContextClassLoader(ccl);
3171: }
3172: } catch (final RuntimeException re) {
3173: reportError("Unable to get default FileTypeMap",
3174: re, ErrorManager.OPEN_FAILURE);
3175: } catch (final LinkageError GLASSFISH_21258) {
3176: reportLinkageError(GLASSFISH_21258, ErrorManager.OPEN_FAILURE);
3177: } catch (final ServiceConfigurationError sce) {
3178: reportConfigurationError(sce, ErrorManager.OPEN_FAILURE);
3179: }
3180: }
3181:
3182: /**
3183: * Parses LogManager string values into objects used by this handler.
3184: *
3185: * @param nameOrNumber the level name, number, or null for OFF.
3186: * @throws SecurityException if not allowed.
3187: */
3188: private void initPushLevel(final String nameOrNumber) {
3189: assert Thread.holdsLock(this);
3190: try {
3191: if (!isEmpty(nameOrNumber)) {
3192: this.pushLevel = Level.parse(nameOrNumber);
3193: } else {
3194: this.pushLevel = Level.OFF;
3195: }
3196: } catch (final RuntimeException RE) {
3197: reportError("Unable to parse push level", RE, ErrorManager.OPEN_FAILURE);
3198: }
3199:
3200: if (this.pushLevel == null) {
3201: this.pushLevel = Level.OFF;
3202: }
3203: }
3204:
3205: /**
3206: * Parses LogManager string values into objects used by this handler.
3207: *
3208: * @param name the push filter class name.
3209: * @throws SecurityException if not allowed.
3210: */
3211: private void initPushFilter(final String name) {
3212: assert Thread.holdsLock(this);
3213: try {
3214: if (hasValue(name)) {
3215: this.pushFilter = LogManagerProperties.newFilter(name);
3216: } else {
3217: this.pushFilter = null;
3218: }
3219: } catch (final SecurityException SE) {
3220: throw SE; //Avoid catch all.
3221: } catch (final Exception E) {
3222: reportError("Unable to create push filter", E, ErrorManager.OPEN_FAILURE);
3223: }
3224: }
3225:
3226: /**
3227: * Creates the subject formatter used by this handler.
3228: *
3229: * @param name the formatter class name, string literal, or null.
3230: * @throws SecurityException if not allowed.
3231: */
3232: private void initSubject(String name) {
3233: assert Thread.holdsLock(this);
3234: if (isWriting) {
3235: throw new IllegalStateException();
3236: }
3237:
3238: if (name == null) { //Soft dependency on CollectorFormatter.
3239: name = "org.eclipse.angus.mail.util.logging.CollectorFormatter";
3240: }
3241:
3242: if (hasValue(name)) {
3243: try {
3244: this.subjectFormatter = LogManagerProperties.newFormatter(name);
3245: } catch (final SecurityException SE) {
3246: throw SE; //Avoid catch all.
3247: } catch (ClassNotFoundException
3248: | ClassCastException literalSubject) {
3249: this.subjectFormatter = TailNameFormatter.of(name);
3250: } catch (final Exception E) {
3251: this.subjectFormatter = TailNameFormatter.of(name);
3252: reportError("Unable to create subject formatter", E, ErrorManager.OPEN_FAILURE);
3253: }
3254: } else { //User has forced empty or literal null.
3255: this.subjectFormatter = TailNameFormatter.of(name);
3256: }
3257: }
3258:
3259: /**
3260: * Check if any attachment would actually format the given
3261: * <code>LogRecord</code>. This method does not check if the handler
3262: * is level is set to OFF or if the handler is closed.
3263: *
3264: * @param record a <code>LogRecord</code>
3265: * @return true if the <code>LogRecord</code> would be formatted.
3266: */
3267: private boolean isAttachmentLoggable(final LogRecord record) {
3268: final Filter[] filters = readOnlyAttachmentFilters();
3269: for (int i = 0; i < filters.length; ++i) {
3270: final Filter f = filters[i];
3271: if (f == null || f.isLoggable(record)) {
3272: setMatchedPart(i);
3273: return true;
3274: }
3275: }
3276: return false;
3277: }
3278:
3279: /**
3280: * Check if this <code>Handler</code> would push after storing the
3281: * <code>LogRecord</code> into its internal buffer.
3282: *
3283: * @param record a <code>LogRecord</code>
3284: * @return true if the <code>LogRecord</code> triggers an email push.
3285: * @throws NullPointerException if tryMutex was not called.
3286: */
3287: private boolean isPushable(final LogRecord record) {
3288: assert Thread.holdsLock(this);
3289: final int value = getPushLevel().intValue();
3290: if (value == offValue || record.getLevel().intValue() < value) {
3291: return false;
3292: }
3293:
3294: final Filter push = getPushFilter();
3295: if (push == null) {
3296: return true;
3297: }
3298:
3299: final int match = getMatchedPart();
3300: if ((match == -1 && getFilter() == push)
3301: || (match >= 0 && attachmentFilters[match] == push)) {
3302: return true;
3303: } else {
3304: return push.isLoggable(record);
3305: }
3306: }
3307:
3308: /**
3309: * Used to perform push or flush.
3310: *
3311: * @param priority true for high priority otherwise false for normal.
3312: * @param code the error manager code.
3313: */
3314: private void push(final boolean priority, final int code) {
3315: if (tryMutex()) {
3316: try {
3317: final Message msg = writeLogRecords(code);
3318: if (msg != null) {
3319: send(msg, priority, code);
3320: }
3321: } catch (final LinkageError JDK8152515) {
3322: reportLinkageError(JDK8152515, code);
3323: } catch (final ServiceConfigurationError sce) {
3324: reportConfigurationError(sce, code);
3325: } finally {
3326: releaseMutex();
3327: }
3328: } else {
3329: reportUnPublishedError((LogRecord) null);
3330: }
3331: }
3332:
3333: /**
3334: * Used to send the generated email or write its contents to the
3335: * error manager for this handler. This method does not hold any
3336: * locks so new records can be added to this handler during a send or
3337: * failure.
3338: *
3339: * @param msg the message or null.
3340: * @param priority true for high priority or false for normal.
3341: * @param code the ErrorManager code.
3342: * @throws NullPointerException if message is null.
3343: */
3344: private void send(Message msg, boolean priority, int code) {
3345: try {
3346: envelopeFor(msg, priority);
3347: final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
3348: try { //JDK-8025251
3349: Transport.send(msg); //Calls save changes.
3350: } finally {
3351: getAndSetContextClassLoader(ccl);
3352: }
3353: } catch (final Exception e) {
3354: reportError(msg, e, code);
3355: }
3356: }
3357:
3358: /**
3359: * Performs a sort on the records if needed.
3360: * Any exception thrown during a sort is considered a formatting error.
3361: */
3362: private void sort() {
3363: assert Thread.holdsLock(this);
3364: if (comparator != null) {
3365: try {
3366: if (size != 1) {
3367: Arrays.sort(data, 0, size, comparator);
3368: } else {
3369: if (comparator.compare(data[0], data[0]) != 0) {
3370: throw new IllegalArgumentException(
3371: comparator.getClass().getName());
3372: }
3373: }
3374: } catch (final RuntimeException RE) {
3375: reportError(comparator.toString(), RE, ErrorManager.FORMAT_FAILURE);
3376: }
3377: }
3378: }
3379:
3380: /**
3381: * Formats all records in the buffer and places the output in a Message.
3382: * This method under most conditions will catch, report, and continue when
3383: * exceptions occur. This method holds a lock on this handler.
3384: *
3385: * @param code the error manager code.
3386: * @return null if there are no records or is currently in a push.
3387: * Otherwise a new message is created with a formatted message and
3388: * attached session.
3389: * @throws ServiceConfigurationError if the session provider fails to
3390: * lookup the provider implementation.
3391: */
3392: private Message writeLogRecords(final int code) {
3393: try {
3394: synchronized (this) {
3395: if (size > 0 && !isWriting) {
3396: isWriting = true;
3397: try {
3398: return writeLogRecords0();
3399: } finally {
3400: isWriting = false;
3401: if (size > 0) {
3402: reset();
3403: }
3404: }
3405: }
3406: }
3407: } catch (final Exception e) {
3408: reportError("Unable to create message", e, code);
3409: }
3410: return null;
3411: }
3412:
3413: /**
3414: * Formats all records in the buffer and places the output in a Message.
3415: * This method under most conditions will catch, report, and continue when
3416: * exceptions occur.
3417: *
3418: * @return null if there are no records or is currently in a push. Otherwise
3419: * a new message is created with a formatted message and attached session.
3420: * @throws MessagingException if there is a problem.
3421: * @throws IOException if there is a problem.
3422: * @throws RuntimeException if there is an unexpected problem.
3423: * @throws ServiceConfigurationError if the session provider fails to
3424: * lookup the provider implementation.
3425: * @since JavaMail 1.5.3
3426: */
3427: private Message writeLogRecords0() throws Exception {
3428: assert Thread.holdsLock(this);
3429: sort();
3430: if (session == null) {
3431: initSession();
3432: }
3433: MimeMessage msg = new MimeMessage(session);
3434:
3435: /**
3436: * Parts are lazily created when an attachment performs a getHead
3437: * call. Therefore, a null part at an index means that the head is
3438: * required.
3439: */
3440: MimeBodyPart[] parts = new MimeBodyPart[attachmentFormatters.length];
3441:
3442: /**
3443: * The buffers are lazily created when the part requires a getHead.
3444: */
3445: StringBuilder[] buffers = new StringBuilder[parts.length];
3446: StringBuilder buf = null;
3447: final MimePart body;
3448: if (parts.length == 0) {
3449: msg.setDescription(descriptionFrom(
3450: getFormatter(), getFilter(), subjectFormatter));
3451: body = msg;
3452: } else {
3453: msg.setDescription(descriptionFrom(
3454: comparator, pushLevel, pushFilter));
3455: body = createBodyPart();
3456: }
3457:
3458: appendSubject(msg, head(subjectFormatter));
3459: final Formatter bodyFormat = getFormatter();
3460: final Filter bodyFilter = getFilter();
3461:
3462: Locale lastLocale = null;
3463: for (int ix = 0; ix < size; ++ix) {
3464: boolean formatted = false;
3465: final int match = matched[ix];
3466: final LogRecord r = data[ix];
3467: data[ix] = null; //Clear while formatting.
3468:
3469: final Locale locale = localeFor(r);
3470: appendSubject(msg, format(subjectFormatter, r));
3471: Filter lmf = null; //Identity of last matched filter.
3472: if (bodyFilter == null || match == -1 || parts.length == 0
3473: || (match < -1 && bodyFilter.isLoggable(r))) {
3474: lmf = bodyFilter;
3475: if (buf == null) {
3476: buf = new StringBuilder();
3477: buf.append(head(bodyFormat));
3478: }
3479: formatted = true;
3480: buf.append(format(bodyFormat, r));
3481: if (locale != null && !locale.equals(lastLocale)) {
3482: appendContentLang(body, locale);
3483: }
3484: }
3485:
3486: for (int i = 0; i < parts.length; ++i) {
3487: //A match index less than the attachment index means that
3488: //the filter has not seen this record.
3489: final Filter af = attachmentFilters[i];
3490: if (af == null || lmf == af || match == i
3491: || (match < i && af.isLoggable(r))) {
3492: if (lmf == null && af != null) {
3493: lmf = af;
3494: }
3495: if (parts[i] == null) {
3496: parts[i] = createBodyPart(i);
3497: buffers[i] = new StringBuilder();
3498: buffers[i].append(head(attachmentFormatters[i]));
3499: appendFileName(parts[i], head(attachmentNames[i]));
3500: }
3501: formatted = true;
3502: appendFileName(parts[i], format(attachmentNames[i], r));
3503: buffers[i].append(format(attachmentFormatters[i], r));
3504: if (locale != null && !locale.equals(lastLocale)) {
3505: appendContentLang(parts[i], locale);
3506: }
3507: }
3508: }
3509:
3510: if (formatted) {
3511: if (body != msg && locale != null
3512: && !locale.equals(lastLocale)) {
3513: appendContentLang(msg, locale);
3514: }
3515: } else { //Belongs to no mime part.
3516: reportFilterError(r);
3517: }
3518: lastLocale = locale;
3519: }
3520: this.size = 0;
3521:
3522: for (int i = parts.length - 1; i >= 0; --i) {
3523: if (parts[i] != null) {
3524: appendFileName(parts[i], tail(attachmentNames[i], "err"));
3525: buffers[i].append(tail(attachmentFormatters[i], ""));
3526:
3527: if (buffers[i].length() > 0) {
3528: String name = parts[i].getFileName();
3529: if (isEmpty(name)) { //Exceptional case.
3530: name = toString(attachmentFormatters[i]);
3531: parts[i].setFileName(name);
3532: }
3533: setContent(parts[i], buffers[i], getContentType(name));
3534: } else {
3535: setIncompleteCopy(msg);
3536: parts[i] = null; //Skip this part.
3537: }
3538: buffers[i] = null;
3539: }
3540: }
3541:
3542: if (buf != null) {
3543: buf.append(tail(bodyFormat, ""));
3544: //This body part is always added, even if the buffer is empty,
3545: //so the body is never considered an incomplete-copy.
3546: } else {
3547: buf = new StringBuilder(0);
3548: }
3549:
3550: appendSubject(msg, tail(subjectFormatter, ""));
3551:
3552: String contentType = contentTypeOf(buf);
3553: String altType = contentTypeOf(bodyFormat);
3554: setContent(body, buf, altType == null ? contentType : altType);
3555: if (body != msg) {
3556: final MimeMultipart multipart = createMultipart();
3557: //assert body instanceof BodyPart : body;
3558: multipart.addBodyPart((BodyPart) body);
3559:
3560: for (int i = 0; i < parts.length; ++i) {
3561: if (parts[i] != null) {
3562: multipart.addBodyPart(parts[i]);
3563: }
3564: }
3565: msg.setContent(multipart);
3566: }
3567:
3568: return msg;
3569: }
3570:
3571: /**
3572: * Checks all of the settings if the caller requests a verify and a verify
3573: * was not performed yet and no verify is in progress. A verify is
3574: * performed on create because this handler may be at the end of a handler
3575: * chain and therefore may not see any log records until LogManager.reset()
3576: * is called and at that time all of the settings have been cleared.
3577: *
3578: * @param session the current session or null.
3579: * @return true if verification key was present.
3580: * @since JavaMail 1.4.4
3581: */
3582: private boolean verifySettings(final Session session) {
3583: try {
3584: if (session == null) {
3585: return false;
3586: }
3587:
3588: final Properties props = session.getProperties();
3589: final Object check = props.put("verify", "");
3590: if (check == null) {
3591: return false;
3592: }
3593:
3594: if (check instanceof String) {
3595: String value = (String) check;
3596: //Perform the verify if needed.
3597: if (hasValue(value)) {
3598: verifySettings0(session, value);
3599: }
3600: return true;
3601: } else { //Pass some invalid string.
3602: verifySettings0(session, check.getClass().toString());
3603: }
3604: } catch (final LinkageError JDK8152515) {
3605: reportLinkageError(JDK8152515, ErrorManager.OPEN_FAILURE);
3606: } catch (final ServiceConfigurationError sce) {
3607: reportConfigurationError(sce, ErrorManager.OPEN_FAILURE);
3608: }
3609: return false;
3610: }
3611:
3612: /**
3613: * Checks all of the settings using the given setting.
3614: * This triggers the LogManagerProperties to copy all of the mail
3615: * settings without explictly knowing them. Once all of the properties
3616: * are copied this handler can handle LogManager.reset clearing all of the
3617: * properties. It is expected that this method is, at most, only called
3618: * once per session.
3619: *
3620: * @param session the current session.
3621: * @param verify the type of verify to perform.
3622: * @since JavaMail 1.4.4
3623: */
3624: private void verifySettings0(Session session, String verify) {
3625: assert verify != null : (String) null;
3626: if (!"local".equals(verify) && !"remote".equals(verify)
3627: && !"limited".equals(verify) && !"resolve".equals(verify)
3628: && !"login".equals(verify)) {
3629: reportError("Verify must be 'limited', local', "
3630: + "'resolve', 'login', or 'remote'.",
3631: new IllegalArgumentException(verify),
3632: ErrorManager.OPEN_FAILURE);
3633: return;
3634: }
3635:
3636: final MimeMessage abort = new MimeMessage(session);
3637: final String msg;
3638: if (!"limited".equals(verify)) {
3639: msg = "Local address is "
3640: + InternetAddress.getLocalAddress(session) + '.';
3641:
3642: try { //Verify subclass or declared mime charset.
3643: Charset.forName(getEncodingName());
3644: } catch (final RuntimeException RE) {
3645: UnsupportedEncodingException UEE =
3646: new UnsupportedEncodingException(RE.toString());
3647: UEE.initCause(RE);
3648: reportError(msg, UEE, ErrorManager.FORMAT_FAILURE);
3649: }
3650: } else {
3651: msg = "Skipping local address check.";
3652: }
3653:
3654: //Perform all of the copy actions first.
3655: String[] atn;
3656: synchronized (this) { //Create the subject.
3657: appendSubject(abort, head(subjectFormatter));
3658: appendSubject(abort, tail(subjectFormatter, ""));
3659: atn = new String[attachmentNames.length];
3660: for (int i = 0; i < atn.length; ++i) {
3661: atn[i] = head(attachmentNames[i]);
3662: if (atn[i].length() == 0) {
3663: atn[i] = tail(attachmentNames[i], "");
3664: } else {
3665: atn[i] = atn[i].concat(tail(attachmentNames[i], ""));
3666: }
3667: }
3668: }
3669:
3670: setIncompleteCopy(abort); //Original body part is never added.
3671: envelopeFor(abort, true);
3672: saveChangesNoContent(abort, msg);
3673: try {
3674: //Ensure transport provider is installed.
3675: Address[] all = abort.getAllRecipients();
3676: if (all == null) { //Don't pass null to sendMessage.
3677: all = new InternetAddress[0];
3678: }
3679: Transport t;
3680: try {
3681: final Address[] any = all.length != 0 ? all : abort.getFrom();
3682: if (any != null && any.length != 0) {
3683: t = session.getTransport(any[0]);
3684: session.getProperty("mail.transport.protocol"); //Force copy
3685: } else {
3686: MessagingException me = new MessagingException(
3687: "No recipient or from address.");
3688: reportError(msg, me, ErrorManager.OPEN_FAILURE);
3689: throw me;
3690: }
3691: } catch (final MessagingException protocol) {
3692: //Switching the CCL emulates the current send behavior.
3693: Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
3694: try {
3695: t = session.getTransport();
3696: } catch (final MessagingException fail) {
3697: throw attach(protocol, fail);
3698: } finally {
3699: getAndSetContextClassLoader(ccl);
3700: }
3701: }
3702:
3703: String local = null;
3704: if ("remote".equals(verify) || "login".equals(verify)) {
3705: MessagingException closed = null;
3706: t.connect();
3707: try {
3708: try {
3709: //Capture localhost while connection is open.
3710: local = getLocalHost(t);
3711:
3712: //A message without content will fail at message writeTo
3713: //when sendMessage is called. This allows the handler
3714: //to capture all mail properties set in the LogManager.
3715: if ("remote".equals(verify)) {
3716: t.sendMessage(abort, all);
3717: }
3718: } finally {
3719: try {
3720: t.close();
3721: } catch (final MessagingException ME) {
3722: closed = ME;
3723: }
3724: }
3725: //Close the transport before reporting errors.
3726: if ("remote".equals(verify)) {
3727: reportUnexpectedSend(abort, verify, null);
3728: } else {
3729: final String protocol = t.getURLName().getProtocol();
3730: verifyProperties(session, protocol);
3731: }
3732: } catch (final SendFailedException sfe) {
3733: Address[] recip = sfe.getInvalidAddresses();
3734: if (recip != null && recip.length != 0) {
3735: setErrorContent(abort, verify, sfe);
3736: reportError(abort, sfe, ErrorManager.OPEN_FAILURE);
3737: }
3738:
3739: recip = sfe.getValidSentAddresses();
3740: if (recip != null && recip.length != 0) {
3741: reportUnexpectedSend(abort, verify, sfe);
3742: }
3743: } catch (final MessagingException ME) {
3744: if (!isMissingContent(abort, ME)) {
3745: setErrorContent(abort, verify, ME);
3746: reportError(abort, ME, ErrorManager.OPEN_FAILURE);
3747: }
3748: }
3749:
3750: if (closed != null) {
3751: setErrorContent(abort, verify, closed);
3752: reportError(abort, closed, ErrorManager.CLOSE_FAILURE);
3753: }
3754: } else {
3755: //Force a property copy, JDK-7092981.
3756: final String protocol = t.getURLName().getProtocol();
3757: verifyProperties(session, protocol);
3758: String mailHost = session.getProperty("mail."
3759: + protocol + ".host");
3760: if (isEmpty(mailHost)) {
3761: mailHost = session.getProperty("mail.host");
3762: } else {
3763: session.getProperty("mail.host");
3764: }
3765:
3766: local = session.getProperty("mail." + protocol + ".localhost");
3767: if (isEmpty(local)) {
3768: local = session.getProperty("mail."
3769: + protocol + ".localaddress");
3770: } else {
3771: session.getProperty("mail." + protocol + ".localaddress");
3772: }
3773:
3774: if ("resolve".equals(verify)) {
3775: try { //Resolve the remote host name.
3776: String transportHost = t.getURLName().getHost();
3777: if (!isEmpty(transportHost)) {
3778: verifyHost(transportHost);
3779: if (!transportHost.equalsIgnoreCase(mailHost)) {
3780: verifyHost(mailHost);
3781: }
3782: } else {
3783: verifyHost(mailHost);
3784: }
3785: } catch (final RuntimeException | IOException IOE) {
3786: MessagingException ME =
3787: new MessagingException(msg, IOE);
3788: setErrorContent(abort, verify, ME);
3789: reportError(abort, ME, ErrorManager.OPEN_FAILURE);
3790: }
3791: }
3792: }
3793:
3794: if (!"limited".equals(verify)) {
3795: try { //Verify host name and hit the host name cache.
3796: if (!"remote".equals(verify) && !"login".equals(verify)) {
3797: local = getLocalHost(t);
3798: }
3799: verifyHost(local);
3800: } catch (final RuntimeException | IOException IOE) {
3801: MessagingException ME = new MessagingException(msg, IOE);
3802: setErrorContent(abort, verify, ME);
3803: reportError(abort, ME, ErrorManager.OPEN_FAILURE);
3804: }
3805:
3806: try { //Verify that the DataHandler can be loaded.
3807: Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
3808: try {
3809: //Always load the multipart classes.
3810: MimeMultipart multipart = new MimeMultipart();
3811: MimeBodyPart[] ambp = new MimeBodyPart[atn.length];
3812: final MimeBodyPart body;
3813: final String bodyContentType;
3814: synchronized (this) {
3815: bodyContentType = contentTypeOf(getFormatter());
3816: body = createBodyPart();
3817: for (int i = 0; i < atn.length; ++i) {
3818: ambp[i] = createBodyPart(i);
3819: ambp[i].setFileName(atn[i]);
3820: //Convert names to mime type under lock.
3821: atn[i] = getContentType(atn[i]);
3822: }
3823: }
3824:
3825: body.setDescription(verify);
3826: setContent(body, "", bodyContentType);
3827: multipart.addBodyPart(body);
3828: for (int i = 0; i < ambp.length; ++i) {
3829: ambp[i].setDescription(verify);
3830: setContent(ambp[i], "", atn[i]);
3831: }
3832:
3833: abort.setContent(multipart);
3834: abort.saveChanges();
3835: abort.writeTo(new ByteArrayOutputStream(MIN_HEADER_SIZE));
3836: } finally {
3837: getAndSetContextClassLoader(ccl);
3838: }
3839: } catch (final IOException IOE) {
3840: MessagingException ME = new MessagingException(msg, IOE);
3841: setErrorContent(abort, verify, ME);
3842: reportError(abort, ME, ErrorManager.FORMAT_FAILURE);
3843: }
3844: }
3845:
3846: //Verify all recipients.
3847: if (all.length != 0) {
3848: verifyAddresses(all);
3849: } else {
3850: throw new MessagingException("No recipient addresses.");
3851: }
3852:
3853: //Verify from and sender addresses.
3854: Address[] from = abort.getFrom();
3855: Address sender = abort.getSender();
3856: if (sender instanceof InternetAddress) {
3857: ((InternetAddress) sender).validate();
3858: }
3859:
3860: //If from address is declared then check sender.
3861: if (abort.getHeader("From", ",") != null && from.length != 0) {
3862: verifyAddresses(from);
3863: for (int i = 0; i < from.length; ++i) {
3864: if (from[i].equals(sender)) {
3865: MessagingException ME = new MessagingException(
3866: "Sender address '" + sender
3867: + "' equals from address.");
3868: throw new MessagingException(msg, ME);
3869: }
3870: }
3871: } else {
3872: if (sender == null) {
3873: MessagingException ME = new MessagingException(
3874: "No from or sender address.");
3875: throw new MessagingException(msg, ME);
3876: }
3877: }
3878:
3879: //Verify reply-to addresses.
3880: verifyAddresses(abort.getReplyTo());
3881: } catch (final Exception ME) {
3882: setErrorContent(abort, verify, ME);
3883: reportError(abort, ME, ErrorManager.OPEN_FAILURE);
3884: }
3885: }
3886:
3887: /**
3888: * Handles all exceptions thrown when save changes is called on a message
3889: * that doesn't have any content.
3890: *
3891: * @param abort the message requiring save changes.
3892: * @param msg the error description.
3893: * @since JavaMail 1.6.0
3894: */
3895: private void saveChangesNoContent(final Message abort, final String msg) {
3896: if (abort != null) {
3897: try {
3898: try {
3899: //The abort message is non-null at this point so if any
3900: //NPE is thrown then it was due to the call to saveChanges.
3901: abort.saveChanges();
3902: } catch (final NullPointerException xferEncoding) {
3903: //Workaround GNU JavaMail bug in MimeUtility.getEncoding
3904: //when the mime message has no content.
3905: try {
3906: String cte = "Content-Transfer-Encoding";
3907: if (abort.getHeader(cte) == null) {
3908: abort.setHeader(cte, EncoderTypes.BASE_64.getEncoder());
3909: abort.saveChanges();
3910: } else {
3911: throw xferEncoding;
3912: }
3913: } catch (RuntimeException | MessagingException e) {
3914: if (e != xferEncoding) {
3915: e.addSuppressed(xferEncoding);
3916: }
3917: throw e;
3918: }
3919: }
3920: } catch (RuntimeException | MessagingException ME) {
3921: reportError(msg, ME, ErrorManager.FORMAT_FAILURE);
3922: }
3923: }
3924: }
3925:
3926: /**
3927: * Cache common session properties into the LogManagerProperties. This is
3928: * a workaround for JDK-7092981.
3929: *
3930: * @param session the session.
3931: * @param protocol the mail protocol.
3932: * @throws NullPointerException if session is null.
3933: * @since JavaMail 1.6.0
3934: */
3935: private static void verifyProperties(Session session, String protocol) {
3936: session.getProperty("mail.from");
3937: session.getProperty("mail." + protocol + ".from");
3938: session.getProperty("mail.dsn.ret");
3939: session.getProperty("mail." + protocol + ".dsn.ret");
3940: session.getProperty("mail.dsn.notify");
3941: session.getProperty("mail." + protocol + ".dsn.notify");
3942: session.getProperty("mail." + protocol + ".port");
3943: session.getProperty("mail.user");
3944: session.getProperty("mail." + protocol + ".user");
3945: session.getProperty("mail." + protocol + ".localport");
3946: }
3947:
3948: /**
3949: * Perform a lookup of the host address or FQDN.
3950: *
3951: * @param host the host or null.
3952: * @return the address.
3953: * @throws IOException if the host name is not valid.
3954: * @throws SecurityException if security manager is present and doesn't
3955: * allow access to check connect permission.
3956: * @since JavaMail 1.5.0
3957: */
3958: private static InetAddress verifyHost(String host) throws IOException {
3959: InetAddress a;
3960: if (isEmpty(host)) {
3961: a = InetAddress.getLocalHost();
3962: } else {
3963: a = InetAddress.getByName(host);
3964: }
3965: if (a.getCanonicalHostName().length() == 0) {
3966: throw new UnknownHostException();
3967: }
3968: return a;
3969: }
3970:
3971: /**
3972: * Calls validate for every address given.
3973: * If the addresses given are null, empty or not an InternetAddress then
3974: * the check is skipped.
3975: *
3976: * @param all any address array, null or empty.
3977: * @throws AddressException if there is a problem.
3978: * @since JavaMail 1.4.5
3979: */
3980: private static void verifyAddresses(Address[] all) throws AddressException {
3981: if (all != null) {
3982: for (int i = 0; i < all.length; ++i) {
3983: final Address a = all[i];
3984: if (a instanceof InternetAddress) {
3985: ((InternetAddress) a).validate();
3986: }
3987: }
3988: }
3989: }
3990:
3991: /**
3992: * Reports that an empty content message was sent and should not have been.
3993: *
3994: * @param msg the MimeMessage.
3995: * @param verify the verify enum.
3996: * @param cause the exception that caused the problem or null.
3997: * @since JavaMail 1.4.5
3998: */
3999: private void reportUnexpectedSend(MimeMessage msg, String verify, Exception cause) {
4000: final MessagingException write = new MessagingException(
4001: "An empty message was sent.", cause);
4002: setErrorContent(msg, verify, write);
4003: reportError(msg, write, ErrorManager.OPEN_FAILURE);
4004: }
4005:
4006: /**
4007: * Creates and sets the message content from the given Throwable.
4008: * When verify fails, this method fixes the 'abort' message so that any
4009: * created envelope data can be used in the error manager.
4010: *
4011: * @param msg the message with or without content.
4012: * @param verify the verify enum.
4013: * @param t the throwable or null.
4014: * @since JavaMail 1.4.5
4015: */
4016: private void setErrorContent(MimeMessage msg, String verify, Throwable t) {
4017: try { //Add content so toRawString doesn't fail.
4018: final MimeBodyPart body;
4019: final String subjectType;
4020: final String msgDesc;
4021: synchronized (this) {
4022: body = createBodyPart();
4023: msgDesc = descriptionFrom(comparator, pushLevel, pushFilter);
4024: subjectType = getClassId(subjectFormatter);
4025: }
4026:
4027: body.setDescription("Formatted using "
4028: + (t == null ? Throwable.class.getName()
4029: : t.getClass().getName()) + ", filtered with "
4030: + verify + ", and named by "
4031: + subjectType + '.');
4032: setContent(body, toMsgString(t), "text/plain");
4033: final MimeMultipart multipart = new MimeMultipart();
4034: multipart.addBodyPart(body);
4035: msg.setContent(multipart);
4036: msg.setDescription(msgDesc);
4037: setAcceptLang(msg);
4038: msg.saveChanges();
4039: } catch (MessagingException | RuntimeException ME) {
4040: reportError("Unable to create body.", ME, ErrorManager.OPEN_FAILURE);
4041: }
4042: }
4043:
4044: /**
4045: * Used to update the cached session object based on changes in
4046: * mail properties or authenticator.
4047: *
4048: * @return the current session or null if no verify is required.
4049: */
4050: private Session updateSession() {
4051: assert Thread.holdsLock(this);
4052: Session settings = null;
4053: if (mailProps.getProperty("verify") != null) {
4054: try {
4055: settings = initSession();
4056: assert settings == session : session;
4057: } catch (final RuntimeException re) {
4058: reportError("Unable to update session",
4059: re, ErrorManager.OPEN_FAILURE);
4060: } catch (final LinkageError GLASSFISH_21258) {
4061: reportLinkageError(GLASSFISH_21258, ErrorManager.OPEN_FAILURE);
4062: } catch (final ServiceConfigurationError sce) {
4063: reportConfigurationError(sce, ErrorManager.OPEN_FAILURE);
4064: }
4065: } else {
4066: session = null; //Remove old session.
4067: settings = null;
4068: }
4069: return settings;
4070: }
4071:
4072: /**
4073: * Creates a session using a proxy properties object.
4074: *
4075: * @return the session that was created and assigned.
4076: * @throws IllegalStateException if the session provider fails to lookup the
4077: * provider implementation.
4078: * @throws ServiceConfigurationError if the session provider fails to
4079: * lookup the provider implementation.
4080: */
4081: private Session initSession() {
4082: assert Thread.holdsLock(this);
4083: final String p = getClass().getName();
4084: final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
4085: try {
4086: LogManagerProperties proxy = new LogManagerProperties(mailProps, p);
4087: session = Session.getInstance(proxy, auth);
4088: } finally {
4089: getAndSetContextClassLoader(ccl);
4090: }
4091: return session;
4092: }
4093:
4094: /**
4095: * Creates all of the envelope information for a message.
4096: * This method is safe to call outside of a lock because the message
4097: * provides the safe snapshot of the mail properties.
4098: *
4099: * @param msg the Message to write the envelope information.
4100: * @param priority true for high priority.
4101: */
4102: private void envelopeFor(Message msg, boolean priority) {
4103: setAcceptLang(msg);
4104: setFrom(msg);
4105: if (!setRecipient(msg, "mail.to", Message.RecipientType.TO)) {
4106: setDefaultRecipient(msg, Message.RecipientType.TO);
4107: }
4108: setRecipient(msg, "mail.cc", Message.RecipientType.CC);
4109: setRecipient(msg, "mail.bcc", Message.RecipientType.BCC);
4110: setReplyTo(msg);
4111: setSender(msg);
4112: setMailer(msg);
4113: setAutoSubmitted(msg);
4114: if (priority) {
4115: setPriority(msg);
4116: }
4117:
4118: try {
4119: msg.setSentDate(new java.util.Date());
4120: } catch (final MessagingException ME) {
4121: reportError("Sent date not set", ME, ErrorManager.FORMAT_FAILURE);
4122: }
4123: }
4124:
4125: /**
4126: * Creates a new MimeMultipart.
4127: *
4128: * @return a new MimeMultipart
4129: * @throws MessagingException if there is a problem.
4130: * @since Angus Mail 2.0.3
4131: */
4132: private MimeMultipart createMultipart() throws MessagingException {
4133: assert Thread.holdsLock(this);
4134: final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
4135: try {
4136: return new MimeMultipart();
4137: } finally {
4138: getAndSetContextClassLoader(ccl);
4139: }
4140: }
4141:
4142: /**
4143: * Factory to create the in-line body part.
4144: *
4145: * @return a body part with default headers set.
4146: * @throws MessagingException if there is a problem.
4147: */
4148: private MimeBodyPart createBodyPart() throws MessagingException {
4149: assert Thread.holdsLock(this);
4150: final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
4151: try {
4152: final MimeBodyPart part = new MimeBodyPart();
4153: part.setDisposition(Part.INLINE);
4154: part.setDescription(descriptionFrom(getFormatter(),
4155: getFilter(), subjectFormatter));
4156: setAcceptLang(part);
4157: return part;
4158: } finally {
4159: getAndSetContextClassLoader(ccl);
4160: }
4161: }
4162:
4163: /**
4164: * Factory to create the attachment body part.
4165: *
4166: * @param index the attachment index.
4167: * @return a body part with default headers set.
4168: * @throws MessagingException if there is a problem.
4169: * @throws IndexOutOfBoundsException if the given index is not an valid
4170: * attachment index.
4171: */
4172: private MimeBodyPart createBodyPart(int index) throws MessagingException {
4173: assert Thread.holdsLock(this);
4174: final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
4175: try {
4176: final MimeBodyPart part = new MimeBodyPart();
4177: part.setDisposition(Part.ATTACHMENT);
4178: part.setDescription(descriptionFrom(
4179: attachmentFormatters[index],
4180: attachmentFilters[index],
4181: attachmentNames[index]));
4182: setAcceptLang(part);
4183: return part;
4184: } finally {
4185: getAndSetContextClassLoader(ccl);
4186: }
4187: }
4188:
4189: /**
4190: * Gets the description for the MimeMessage itself.
4191: * The push level and filter are included because they play a role in
4192: * formatting of a message when triggered or not triggered.
4193: *
4194: * @param c the comparator.
4195: * @param l the pushLevel.
4196: * @param f the pushFilter
4197: * @return the description.
4198: * @throws NullPointerException if level is null.
4199: * @since JavaMail 1.4.5
4200: */
4201: private String descriptionFrom(Comparator<?> c, Level l, Filter f) {
4202: return "Sorted using " + (c == null ? "no comparator"
4203: : c.getClass().getName()) + ", pushed when " + l.getName()
4204: + ", and " + (f == null ? "no push filter"
4205: : f.getClass().getName()) + '.';
4206: }
4207:
4208: /**
4209: * Creates a description for a body part.
4210: *
4211: * @param f the content formatter.
4212: * @param filter the content filter.
4213: * @param name the naming formatter.
4214: * @return the description for the body part.
4215: */
4216: private String descriptionFrom(Formatter f, Filter filter, Formatter name) {
4217: return "Formatted using " + getClassId(f)
4218: + ", filtered with " + (filter == null ? "no filter"
4219: : filter.getClass().getName()) + ", and named by "
4220: + getClassId(name) + '.';
4221: }
4222:
4223: /**
4224: * Gets a class name represents the behavior of the formatter.
4225: * The class name may not be assignable to a Formatter.
4226: *
4227: * @param f the formatter or null.
4228: * @return a class name that represents the given formatter.
4229: * @since JavaMail 1.4.5
4230: */
4231: private String getClassId(final Formatter f) {
4232: if (f == null) {
4233: return "no formatter";
4234: }
4235:
4236: if (f instanceof TailNameFormatter) {
4237: return String.class.getName(); //Literal string.
4238: } else {
4239: return f.getClass().getName();
4240: }
4241: }
4242:
4243: /**
4244: * Ensure that a formatter creates a valid string for a part name.
4245: *
4246: * @param f the formatter.
4247: * @return the to string value or the class name.
4248: */
4249: private String toString(final Formatter f) {
4250: //Should never be null but, guard against formatter bugs.
4251: final String name = f.toString();
4252: if (!isEmpty(name)) {
4253: return name;
4254: } else {
4255: return getClassId(f);
4256: }
4257: }
4258:
4259: /**
4260: * Constructs a file name from a formatter. This method is called often
4261: * but, rarely does any work.
4262: *
4263: * @param part to append to.
4264: * @param chunk non null string to append.
4265: */
4266: private void appendFileName(final Part part, final String chunk) {
4267: if (chunk != null) {
4268: if (chunk.length() > 0) {
4269: appendFileName0(part, chunk);
4270: }
4271: } else {
4272: reportNullError(ErrorManager.FORMAT_FAILURE);
4273: }
4274: }
4275:
4276: /**
4277: * It is assumed that file names are short and that in most cases
4278: * getTail will be the only method that will produce a result.
4279: *
4280: * @param part to append to.
4281: * @param chunk non null string to append.
4282: */
4283: private void appendFileName0(final Part part, String chunk) {
4284: try {
4285: //Remove all control character groups.
4286: chunk = chunk.replaceAll("[\\x00-\\x1F\\x7F]+", "");
4287: final String old = part.getFileName();
4288: part.setFileName(old != null ? old.concat(chunk) : chunk);
4289: } catch (final MessagingException ME) {
4290: reportError("File name truncated", ME, ErrorManager.FORMAT_FAILURE);
4291: }
4292: }
4293:
4294: /**
4295: * Constructs a subject line from a formatter.
4296: *
4297: * @param msg to append to.
4298: * @param chunk non null string to append.
4299: */
4300: private void appendSubject(final Message msg, final String chunk) {
4301: if (chunk != null) {
4302: if (chunk.length() > 0) {
4303: appendSubject0(msg, chunk);
4304: }
4305: } else {
4306: reportNullError(ErrorManager.FORMAT_FAILURE);
4307: }
4308: }
4309:
4310: /**
4311: * It is assumed that subject lines are short and that in most cases
4312: * getTail will be the only method that will produce a result.
4313: *
4314: * @param msg to append to.
4315: * @param chunk non null string to append.
4316: */
4317: private void appendSubject0(final Message msg, String chunk) {
4318: try {
4319: //Remove all control character groups.
4320: chunk = chunk.replaceAll("[\\x00-\\x1F\\x7F]+", "");
4321: final String charset = getEncodingName();
4322: final String old = msg.getSubject();
4323: assert msg instanceof MimeMessage : msg;
4324: ((MimeMessage) msg).setSubject(old != null ? old.concat(chunk)
4325: : chunk, MimeUtility.mimeCharset(charset));
4326: } catch (final MessagingException ME) {
4327: reportError("Subject truncated", ME, ErrorManager.FORMAT_FAILURE);
4328: }
4329: }
4330:
4331: /**
4332: * Gets the locale for the given log record from the resource bundle.
4333: * If the resource bundle is using the root locale then the default locale
4334: * is returned.
4335: *
4336: * @param r the log record.
4337: * @return null if not localized otherwise, the locale of the record.
4338: * @since JavaMail 1.4.5
4339: */
4340: private Locale localeFor(final LogRecord r) {
4341: Locale l;
4342: final ResourceBundle rb = r.getResourceBundle();
4343: if (rb != null) {
4344: l = rb.getLocale();
4345: if (l == null || isEmpty(l.getLanguage())) {
4346: //The language of the fallback bundle (root) is unknown.
4347: //1. Use default locale. Should only be wrong if the app is
4348: // used with a langauge that was unintended. (unlikely)
4349: //2. Mark it as not localized (force null, info loss).
4350: //3. Use the bundle name (encoded) as an experimental language.
4351: l = Locale.getDefault();
4352: }
4353: } else {
4354: l = null;
4355: }
4356: return l;
4357: }
4358:
4359: /**
4360: * Appends the content language to the given mime part.
4361: * The language tag is only appended if the given language has not been
4362: * specified. This method is only used when we have LogRecords that are
4363: * localized with an assigned resource bundle.
4364: *
4365: * @param p the mime part.
4366: * @param l the locale to append.
4367: * @throws NullPointerException if any argument is null.
4368: * @since JavaMail 1.4.5
4369: */
4370: private void appendContentLang(final MimePart p, final Locale l) {
4371: try {
4372: String lang = LogManagerProperties.toLanguageTag(l);
4373: if (lang.length() != 0) {
4374: String header = p.getHeader("Content-Language",
4375: (String) null);
4376: if (isEmpty(header)) {
4377: p.setHeader("Content-Language", lang);
4378: } else if (!header.equalsIgnoreCase(lang)) {
4379: lang = ",".concat(lang);
4380: int idx = 0;
4381: while ((idx = header.indexOf(lang, idx)) > -1) {
4382: idx += lang.length();
4383: if (idx == header.length()
4384: || header.charAt(idx) == ',') {
4385: break;
4386: }
4387: }
4388:
4389: if (idx < 0) {
4390: int len = header.lastIndexOf("\r\n\t");
4391: if (len < 0) { //If not folded.
4392: len = (18 + 2) + header.length();
4393: } else {
4394: len = (header.length() - len) + 8;
4395: }
4396:
4397: //Perform folding of header if needed.
4398: if ((len + lang.length()) > 76) {
4399: header = header.concat("\r\n\t".concat(lang));
4400: } else {
4401: header = header.concat(lang);
4402: }
4403: p.setHeader("Content-Language", header);
4404: }
4405: }
4406: }
4407: } catch (final MessagingException ME) {
4408: reportError("Content-Language not set",
4409: ME, ErrorManager.FORMAT_FAILURE);
4410: }
4411: }
4412:
4413: /**
4414: * Sets the accept language to the default locale of the JVM.
4415: * If the locale is the root locale the header is not added.
4416: *
4417: * @param p the part to set.
4418: * @since JavaMail 1.4.5
4419: */
4420: private void setAcceptLang(final Part p) {
4421: try {
4422: final String lang = LogManagerProperties
4423: .toLanguageTag(Locale.getDefault());
4424: if (lang.length() != 0) {
4425: p.setHeader("Accept-Language", lang);
4426: }
4427: } catch (final MessagingException ME) {
4428: reportError("Accept-Language not set",
4429: ME, ErrorManager.FORMAT_FAILURE);
4430: }
4431: }
4432:
4433: /**
4434: * Used when a log record was loggable prior to being inserted
4435: * into the buffer but at the time of formatting was no longer loggable.
4436: * Filters were changed after publish but prior to a push or a bug in the
4437: * body filter or one of the attachment filters.
4438: *
4439: * @param record that was not formatted.
4440: * @since JavaMail 1.4.5
4441: */
4442: private void reportFilterError(final LogRecord record) {
4443: assert Thread.holdsLock(this);
4444: final Formatter f = createSimpleFormatter();
4445: final String msg = "Log record " + record.getSequenceNumber()
4446: + " was filtered from all message parts. "
4447: + head(f) + format(f, record) + tail(f, "");
4448: final String txt = getFilter() + ", "
4449: + Arrays.asList(readOnlyAttachmentFilters());
4450: reportError(msg, new IllegalArgumentException(txt),
4451: ErrorManager.FORMAT_FAILURE);
4452: }
4453:
4454: /**
4455: * Reports symmetric contract violations an equals implementation.
4456: *
4457: * @param o the test object must be non null.
4458: * @param found the possible intern, must be non null.
4459: * @throws NullPointerException if any argument is null.
4460: * @since JavaMail 1.5.0
4461: */
4462: private void reportNonSymmetric(final Object o, final Object found) {
4463: reportError("Non symmetric equals implementation."
4464: , new IllegalArgumentException(o.getClass().getName()
4465: + " is not equal to " + found.getClass().getName())
4466: , ErrorManager.OPEN_FAILURE);
4467: }
4468:
4469: /**
4470: * Reports equals implementations that do not discriminate between objects
4471: * of different types or subclass types.
4472: *
4473: * @param o the test object must be non null.
4474: * @param found the possible intern, must be non null.
4475: * @throws NullPointerException if any argument is null.
4476: * @since JavaMail 1.5.0
4477: */
4478: private void reportNonDiscriminating(final Object o, final Object found) {
4479: reportError("Non discriminating equals implementation."
4480: , new IllegalArgumentException(o.getClass().getName()
4481: + " should not be equal to " + found.getClass().getName())
4482: , ErrorManager.OPEN_FAILURE);
4483: }
4484:
4485: /**
4486: * Used to outline the bytes to report a null pointer exception.
4487: * See BUD ID 6533165.
4488: *
4489: * @param code the ErrorManager code.
4490: */
4491: private void reportNullError(final int code) {
4492: reportError("null", new NullPointerException(), code);
4493: }
4494:
4495: /**
4496: * Creates the head or reports a formatting error.
4497: *
4498: * @param f the formatter.
4499: * @return the head string or an empty string.
4500: */
4501: private String head(final Formatter f) {
4502: try {
4503: return f.getHead(this);
4504: } catch (final RuntimeException RE) {
4505: reportError("head", RE, ErrorManager.FORMAT_FAILURE);
4506: return "";
4507: }
4508: }
4509:
4510: /**
4511: * Creates the formatted log record or reports a formatting error.
4512: *
4513: * @param f the formatter.
4514: * @param r the log record.
4515: * @return the formatted string or an empty string.
4516: */
4517: private String format(final Formatter f, final LogRecord r) {
4518: try {
4519: return f.format(r);
4520: } catch (final RuntimeException RE) {
4521: reportError("format", RE, ErrorManager.FORMAT_FAILURE);
4522: return "";
4523: }
4524: }
4525:
4526: /**
4527: * Creates the tail or reports a formatting error.
4528: *
4529: * @param f the formatter.
4530: * @param def the default string to use when there is an error.
4531: * @return the tail string or the given default string.
4532: */
4533: private String tail(final Formatter f, final String def) {
4534: try {
4535: return f.getTail(this);
4536: } catch (final RuntimeException RE) {
4537: reportError("tail", RE, ErrorManager.FORMAT_FAILURE);
4538: return def;
4539: }
4540: }
4541:
4542: /**
4543: * Sets the x-mailer header.
4544: *
4545: * @param msg the target message.
4546: */
4547: private void setMailer(final Message msg) {
4548: try {
4549: final Class<?> mail = MailHandler.class;
4550: final Class<?> k = getClass();
4551: String value;
4552: if (k == mail) {
4553: value = mail.getName();
4554: } else {
4555: try {
4556: value = MimeUtility.encodeText(k.getName());
4557: } catch (final UnsupportedEncodingException E) {
4558: reportError(k.getName(), E, ErrorManager.FORMAT_FAILURE);
4559: value = k.getName().replaceAll("[^\\x00-\\x7F]", "\uu001A");
4560: }
4561: value = MimeUtility.fold(10, mail.getName() + " using the "
4562: + value + " extension.");
4563: }
4564: msg.setHeader("X-Mailer", value);
4565: } catch (final MessagingException ME) {
4566: reportError("X-Mailer not set",
4567: ME, ErrorManager.FORMAT_FAILURE);
4568: }
4569: }
4570:
4571: /**
4572: * Sets the priority and importance headers.
4573: *
4574: * @param msg the target message.
4575: */
4576: private void setPriority(final Message msg) {
4577: try {
4578: msg.setHeader("Importance", "High");
4579: msg.setHeader("Priority", "urgent");
4580: msg.setHeader("X-Priority", "2"); //High
4581: } catch (final MessagingException ME) {
4582: reportError("Importance and priority not set",
4583: ME, ErrorManager.FORMAT_FAILURE);
4584: }
4585: }
4586:
4587: /**
4588: * Used to signal that body parts are missing from a message. Also used
4589: * when LogRecords were passed to an attachment formatter but the formatter
4590: * produced no output, which is allowed. Used during a verify because all
4591: * parts are omitted, none of the content formatters are used. This is
4592: * not used when a filter prevents LogRecords from being formatted.
4593: * This header is defined in RFC 2156 and RFC 4021.
4594: *
4595: * @param msg the message.
4596: * @since JavaMail 1.4.5
4597: */
4598: private void setIncompleteCopy(final Message msg) {
4599: try {
4600: msg.setHeader("Incomplete-Copy", "");
4601: } catch (final MessagingException ME) {
4602: reportError("Incomplete-Copy not set",
4603: ME, ErrorManager.FORMAT_FAILURE);
4604: }
4605: }
4606:
4607: /**
4608: * Signals that this message was generated by automatic process.
4609: * This header is defined in RFC 3834 section 5.
4610: *
4611: * @param msg the message.
4612: * @since JavaMail 1.4.6
4613: */
4614: private void setAutoSubmitted(final Message msg) {
4615: if (allowRestrictedHeaders()) {
4616: try { //RFC 3834 (5.2)
4617: msg.setHeader("auto-submitted", "auto-generated");
4618: } catch (final MessagingException ME) {
4619: reportError("auto-submitted not set",
4620: ME, ErrorManager.FORMAT_FAILURE);
4621: }
4622: }
4623: }
4624:
4625: /**
4626: * Sets from address header.
4627: *
4628: * @param msg the target message.
4629: */
4630: private void setFrom(final Message msg) {
4631: final String from = getSession(msg).getProperty("mail.from");
4632: if (from != null) {
4633: try {
4634: final Address[] address = InternetAddress.parse(from, false);
4635: if (address.length > 0) {
4636: if (address.length == 1) {
4637: msg.setFrom(address[0]);
4638: } else { //Greater than 1 address.
4639: msg.addFrom(address);
4640: }
4641: }
4642: //Can't place an else statement here because the 'from' is
4643: //not null which causes the local address computation
4644: //to fail. Assume the user wants to omit the from address
4645: //header.
4646: } catch (final MessagingException ME) {
4647: reportError("From address not set",
4648: ME, ErrorManager.FORMAT_FAILURE);
4649: setDefaultFrom(msg);
4650: }
4651: } else {
4652: setDefaultFrom(msg);
4653: }
4654: }
4655:
4656: /**
4657: * Sets the from header to the local address.
4658: *
4659: * @param msg the target message.
4660: */
4661: private void setDefaultFrom(final Message msg) {
4662: try {
4663: msg.setFrom();
4664: } catch (final MessagingException ME) {
4665: reportError("Default from address not set",
4666: ME, ErrorManager.FORMAT_FAILURE);
4667: }
4668: }
4669:
4670: /**
4671: * Computes the default to-address if none was specified. This can
4672: * fail if the local address can't be computed.
4673: *
4674: * @param msg the message
4675: * @param type the recipient type.
4676: * @since JavaMail 1.5.0
4677: */
4678: private void setDefaultRecipient(final Message msg,
4679: final Message.RecipientType type) {
4680: try {
4681: Address a = InternetAddress.getLocalAddress(getSession(msg));
4682: if (a != null) {
4683: msg.setRecipient(type, a);
4684: } else {
4685: final MimeMessage m = new MimeMessage(getSession(msg));
4686: m.setFrom(); //Should throw an exception with a cause.
4687: Address[] from = m.getFrom();
4688: if (from.length > 0) {
4689: msg.setRecipients(type, from);
4690: } else {
4691: throw new MessagingException("No local address.");
4692: }
4693: }
4694: } catch (MessagingException | RuntimeException ME) {
4695: reportError("Default recipient not set",
4696: ME, ErrorManager.FORMAT_FAILURE);
4697: }
4698: }
4699:
4700: /**
4701: * Sets reply-to address header.
4702: *
4703: * @param msg the target message.
4704: */
4705: private void setReplyTo(final Message msg) {
4706: final String reply = getSession(msg).getProperty("mail.reply.to");
4707: if (!isEmpty(reply)) {
4708: try {
4709: final Address[] address = InternetAddress.parse(reply, false);
4710: if (address.length > 0) {
4711: msg.setReplyTo(address);
4712: }
4713: } catch (final MessagingException ME) {
4714: reportError("Reply-To address not set",
4715: ME, ErrorManager.FORMAT_FAILURE);
4716: }
4717: }
4718: }
4719:
4720: /**
4721: * Sets sender address header.
4722: *
4723: * @param msg the target message.
4724: */
4725: private void setSender(final Message msg) {
4726: assert msg instanceof MimeMessage : msg;
4727: final String sender = getSession(msg).getProperty("mail.sender");
4728: if (!isEmpty(sender)) {
4729: try {
4730: final InternetAddress[] address =
4731: InternetAddress.parse(sender, false);
4732: if (address.length > 0) {
4733: ((MimeMessage) msg).setSender(address[0]);
4734: if (address.length > 1) {
4735: reportError("Ignoring other senders.",
4736: tooManyAddresses(address, 1),
4737: ErrorManager.FORMAT_FAILURE);
4738: }
4739: }
4740: } catch (final MessagingException ME) {
4741: reportError("Sender not set", ME, ErrorManager.FORMAT_FAILURE);
4742: }
4743: }
4744: }
4745:
4746: /**
4747: * A common factory used to create the too many addresses exception.
4748: *
4749: * @param address the addresses, never null.
4750: * @param offset the starting address to display.
4751: * @return the too many addresses exception.
4752: */
4753: private AddressException tooManyAddresses(Address[] address, int offset) {
4754: Object l = Arrays.asList(address).subList(offset, address.length);
4755: return new AddressException(l.toString());
4756: }
4757:
4758: /**
4759: * Sets the recipient for the given message.
4760: *
4761: * @param msg the message.
4762: * @param key the key to search in the session.
4763: * @param type the recipient type.
4764: * @return true if the key was contained in the session.
4765: */
4766: private boolean setRecipient(final Message msg,
4767: final String key, final Message.RecipientType type) {
4768: boolean containsKey;
4769: final String value = getSession(msg).getProperty(key);
4770: containsKey = value != null;
4771: if (!isEmpty(value)) {
4772: try {
4773: final Address[] address = InternetAddress.parse(value, false);
4774: if (address.length > 0) {
4775: msg.setRecipients(type, address);
4776: }
4777: } catch (final MessagingException ME) {
4778: reportError(key, ME, ErrorManager.FORMAT_FAILURE);
4779: }
4780: }
4781: return containsKey;
4782: }
4783:
4784: /**
4785: * Converts an email message to a raw string. This raw string
4786: * is passed to the error manager to allow custom error managers
4787: * to recreate the original MimeMessage object.
4788: *
4789: * @param msg a Message object.
4790: * @return the raw string or null if msg was null.
4791: * @throws MessagingException if there was a problem with the message.
4792: * @throws IOException if there was a problem.
4793: */
4794: private String toRawString(final Message msg) throws MessagingException, IOException {
4795: if (msg != null) {
4796: Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
4797: try { //JDK-8025251
4798: int nbytes = Math.max(msg.getSize() + MIN_HEADER_SIZE, MIN_HEADER_SIZE);
4799: ByteArrayOutputStream out = new ByteArrayOutputStream(nbytes);
4800: msg.writeTo(out); //Headers can be UTF-8 or US-ASCII.
4801: return out.toString("UTF-8");
4802: } finally {
4803: getAndSetContextClassLoader(ccl);
4804: }
4805: } else { //Must match this.reportError behavior, see push method.
4806: return null; //Null is the safe choice.
4807: }
4808: }
4809:
4810: /**
4811: * Converts a throwable to a message string.
4812: *
4813: * @param t any throwable or null.
4814: * @return the throwable with a stack trace or the literal null.
4815: */
4816: private String toMsgString(final Throwable t) {
4817: if (t == null) {
4818: return "null";
4819: }
4820:
4821: final String charset = getEncodingName();
4822: try {
4823: final ByteArrayOutputStream out =
4824: new ByteArrayOutputStream(MIN_HEADER_SIZE);
4825:
4826: //Create an output stream writer so streams are not double buffered.
4827: try (OutputStreamWriter ows = new OutputStreamWriter(out, charset);
4828: PrintWriter pw = new PrintWriter(ows)) {
4829: pw.println(t.getMessage());
4830: t.printStackTrace(pw);
4831: pw.flush();
4832: } //Close OSW before generating string. JDK-6995537
4833: return out.toString(charset);
4834: } catch (final Exception badMimeCharset) {
4835: return t.toString() + ' ' + badMimeCharset.toString();
4836: }
4837: }
4838:
4839: /**
4840: * Replaces the current context class loader with our class loader.
4841: *
4842: * @param ccl null for boot class loader, a class loader, a class used to
4843: * get the class loader, or a source object to get the class loader.
4844: * @return null for the boot class loader, a class loader, or a marker
4845: * object to signal that no modification was required.
4846: * @since JavaMail 1.5.3
4847: */
4848: private Object getAndSetContextClassLoader(final Object ccl) {
4849: if (ccl != GetAndSetContext.NOT_MODIFIED) {
4850: try {
4851: final PrivilegedAction<?> pa;
4852: if (ccl instanceof PrivilegedAction) {
4853: pa = (PrivilegedAction<?>) ccl;
4854: } else {
4855: pa = new GetAndSetContext(ccl);
4856: }
4857: return LogManagerProperties.runOrDoPrivileged(pa);
4858: } catch (final SecurityException ignore) {
4859: }
4860: }
4861: return GetAndSetContext.NOT_MODIFIED;
4862: }
4863:
4864: /**
4865: * A factory used to create a common attachment mismatch type.
4866: *
4867: * @param msg the exception message.
4868: * @return a RuntimeException to represent the type of error.
4869: */
4870: private static RuntimeException attachmentMismatch(final String msg) {
4871: return new IndexOutOfBoundsException(msg);
4872: }
4873:
4874: /**
4875: * Try to attach a suppressed exception to a MessagingException in any order
4876: * that is possible.
4877: *
4878: * @param required the exception expected to see as a reported failure.
4879: * @param optional the suppressed exception.
4880: * @return either the required or the optional exception.
4881: */
4882: private static MessagingException attach(
4883: MessagingException required, Exception optional) {
4884: if (optional != null && !required.setNextException(optional)) {
4885: if (optional instanceof MessagingException) {
4886: final MessagingException head = (MessagingException) optional;
4887: if (head.setNextException(required)) {
4888: return head;
4889: }
4890: }
4891:
4892: if (optional != required) {
4893: required.addSuppressed(optional);
4894: }
4895: }
4896: return required;
4897: }
4898:
4899: /**
4900: * Gets the local host from the given service object.
4901: *
4902: * @param s the service to check.
4903: * @return the local host or null.
4904: * @since JavaMail 1.5.3
4905: */
4906: private String getLocalHost(final Service s) {
4907: try {
4908: return LogManagerProperties.getLocalHost(s);
4909: } catch (SecurityException | NoSuchMethodException
4910: | LinkageError ignore) {
4911: } catch (final Exception ex) {
4912: reportError(String.valueOf(s), ex, ErrorManager.OPEN_FAILURE);
4913: }
4914: return null;
4915: }
4916:
4917: /**
4918: * Google App Engine doesn't support Message.getSession.
4919: *
4920: * @param msg the message.
4921: * @return the session from the given message.
4922: * @throws NullPointerException if the given message is null.
4923: * @since JavaMail 1.5.3
4924: */
4925: private Session getSession(final Message msg) {
4926: Objects.requireNonNull(msg);
4927: return new MessageContext(msg).getSession();
4928: }
4929:
4930: /**
4931: * Determines if restricted headers are allowed in the current environment.
4932: *
4933: * @return true if restricted headers are allowed.
4934: * @since JavaMail 1.5.3
4935: */
4936: private boolean allowRestrictedHeaders() {
4937: //GAE will prevent delivery of email with forbidden headers.
4938: //Assume the environment is GAE if access to the LogManager is
4939: //forbidden.
4940: return LogManagerProperties.hasLogManager();
4941: }
4942:
4943: /**
4944: * Used for storing a password from the LogManager or literal string.
4945: *
4946: * @since JavaMail 1.4.6
4947: */
4948: private static final class DefaultAuthenticator extends Authenticator {
4949:
4950: /**
4951: * Creates an Authenticator for the given password. This method is used
4952: * so class verification of assignments in MailHandler doesn't require
4953: * loading this class which otherwise can occur when using the
4954: * constructor. Default access to avoid generating extra class files.
4955: *
4956: * @param pass the password.
4957: * @return an Authenticator for the password.
4958: * @since JavaMail 1.5.6
4959: */
4960: static Authenticator of(final String pass) {
4961: return new DefaultAuthenticator(pass);
4962: }
4963:
4964: /**
4965: * The password to use.
4966: */
4967: private final String pass;
4968:
4969: /**
4970: * Use the factory method instead of this constructor.
4971: *
4972: * @param pass the password.
4973: */
4974: private DefaultAuthenticator(final String pass) {
4975: assert pass != null;
4976: this.pass = pass;
4977: }
4978:
4979: @Override
4980: protected final PasswordAuthentication getPasswordAuthentication() {
4981: return new PasswordAuthentication(getDefaultUserName(), pass);
4982: }
4983: }
4984:
4985: /**
4986: * Performs a get and set of the context class loader with privileges
4987: * enabled.
4988: *
4989: * @since JavaMail 1.4.6
4990: */
4991: private static final class GetAndSetContext implements PrivilegedAction<Object> {
4992: /**
4993: * A marker object used to signal that the class loader was not
4994: * modified.
4995: */
4996: public static final Object NOT_MODIFIED = GetAndSetContext.class;
4997: /**
4998: * The source containing the class loader.
4999: */
5000: private final Object source;
5001:
5002: /**
5003: * Create the action.
5004: *
5005: * @param source null for boot class loader, a class loader, a class
5006: * used to get the class loader, or a source object to get the class
5007: * loader. Default access to avoid generating extra class files.
5008: */
5009: GetAndSetContext(final Object source) {
5010: this.source = source;
5011: }
5012:
5013: /**
5014: * Gets the class loader from the source and sets the CCL only if
5015: * the source and CCL are not the same.
5016: *
5017: * @return the replaced context class loader which can be null or
5018: * NOT_MODIFIED to indicate that nothing was modified.
5019: */
5020: @SuppressWarnings("override") //JDK-6954234
5021: public final Object run() {
5022: final Thread current = Thread.currentThread();
5023: final ClassLoader ccl = current.getContextClassLoader();
5024: final ClassLoader loader;
5025: if (source == null) {
5026: loader = null; //boot class loader
5027: } else if (source instanceof ClassLoader) {
5028: loader = (ClassLoader) source;
5029: } else if (source instanceof Class) {
5030: loader = ((Class<?>) source).getClassLoader();
5031: } else if (source instanceof Thread) {
5032: loader = ((Thread) source).getContextClassLoader();
5033: } else {
5034: assert !(source instanceof Class) : source;
5035: loader = source.getClass().getClassLoader();
5036: }
5037:
5038: if (ccl != loader) {
5039: current.setContextClassLoader(loader);
5040: return ccl;
5041: } else {
5042: return NOT_MODIFIED;
5043: }
5044: }
5045: }
5046:
5047: /**
5048: * Used for naming attachment file names and the main subject line.
5049: */
5050: private static final class TailNameFormatter extends Formatter {
5051:
5052: /**
5053: * Creates or gets a formatter from the given name. This method is used
5054: * so class verification of assignments in MailHandler doesn't require
5055: * loading this class which otherwise can occur when using the
5056: * constructor. Default access to avoid generating extra class files.
5057: *
5058: * @param name any not null string.
5059: * @return a formatter for that string.
5060: * @since JavaMail 1.5.6
5061: */
5062: static Formatter of(final String name) {
5063: return new TailNameFormatter(name);
5064: }
5065:
5066: /**
5067: * The value used as the output.
5068: */
5069: private final String name;
5070:
5071: /**
5072: * Use the factory method instead of this constructor.
5073: *
5074: * @param name any not null string.
5075: */
5076: private TailNameFormatter(final String name) {
5077:• assert name != null;
5078: this.name = name;
5079: }
5080:
5081: @Override
5082: public final String format(LogRecord record) {
5083: return "";
5084: }
5085:
5086: @Override
5087: public final String getTail(Handler h) {
5088: return name;
5089: }
5090:
5091: /**
5092: * Equals method.
5093: *
5094: * @param o the other object.
5095: * @return true if equal
5096: * @since JavaMail 1.4.4
5097: */
5098: @Override
5099: public final boolean equals(Object o) {
5100:• if (o instanceof TailNameFormatter) {
5101: return name.equals(((TailNameFormatter) o).name);
5102: }
5103: return false;
5104: }
5105:
5106: /**
5107: * Hash code method.
5108: *
5109: * @return the hash code.
5110: * @since JavaMail 1.4.4
5111: */
5112: @Override
5113: public final int hashCode() {
5114: return getClass().hashCode() + name.hashCode();
5115: }
5116:
5117: @Override
5118: public final String toString() {
5119: return name;
5120: }
5121: }
5122: }