JAL-3026 srcjar files for VARNA and log4j
[jalview.git] / srcjar / org / apache / log4j / net / SMTPAppender.java
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  * 
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  * 
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 package org.apache.log4j.net;
19
20 import org.apache.log4j.AppenderSkeleton;
21 import org.apache.log4j.Layout;
22 import org.apache.log4j.Level;
23 import org.apache.log4j.helpers.CyclicBuffer;
24 import org.apache.log4j.helpers.LogLog;
25 import org.apache.log4j.helpers.OptionConverter;
26 import org.apache.log4j.spi.ErrorCode;
27 import org.apache.log4j.spi.LoggingEvent;
28 import org.apache.log4j.spi.OptionHandler;
29 import org.apache.log4j.spi.TriggeringEventEvaluator;
30 import org.apache.log4j.xml.UnrecognizedElementHandler;
31 import org.w3c.dom.Element;
32
33 import javax.mail.Authenticator;
34 import javax.mail.Message;
35 import javax.mail.MessagingException;
36 import javax.mail.Multipart;
37 import javax.mail.PasswordAuthentication;
38 import javax.mail.Session;
39 import javax.mail.Transport;
40 import javax.mail.internet.AddressException;
41 import javax.mail.internet.InternetAddress;
42 import javax.mail.internet.InternetHeaders;
43 import javax.mail.internet.MimeBodyPart;
44 import javax.mail.internet.MimeMessage;
45 import javax.mail.internet.MimeMultipart;
46 import javax.mail.internet.MimeUtility;
47 import java.io.ByteArrayOutputStream;
48 import java.io.OutputStreamWriter;
49 import java.io.UnsupportedEncodingException;
50 import java.io.Writer;
51 import java.util.Date;
52 import java.util.Properties;
53
54 /**
55    Send an e-mail when a specific logging event occurs, typically on
56    errors or fatal errors.
57
58    <p>The number of logging events delivered in this e-mail depend on
59    the value of <b>BufferSize</b> option. The
60    <code>SMTPAppender</code> keeps only the last
61    <code>BufferSize</code> logging events in its cyclic buffer. This
62    keeps memory requirements at a reasonable level while still
63    delivering useful application context.
64
65    By default, an email message will be sent when an ERROR or higher
66    severity message is appended.  The triggering criteria can be
67    modified by setting the evaluatorClass property with the name
68    of a class implementing TriggeringEventEvaluator, setting the evaluator
69    property with an instance of TriggeringEventEvaluator or
70    nesting a triggeringPolicy element where the specified
71    class implements TriggeringEventEvaluator.
72    
73    This class has implemented UnrecognizedElementHandler since 1.2.15.
74
75    Since 1.2.16, SMTP over SSL is supported by setting SMTPProtocol to "smpts".
76
77    @author Ceki G&uuml;lc&uuml;
78    @since 1.0 */
79 public class SMTPAppender extends AppenderSkeleton
80         implements UnrecognizedElementHandler {
81   private String to;
82   /**
83    * Comma separated list of cc recipients.
84    */
85   private String cc;  
86   /**
87    * Comma separated list of bcc recipients.
88    */
89   private String bcc;  
90   private String from;
91   /**
92    * Comma separated list of replyTo addresses.
93    */
94   private String replyTo;
95   private String subject;
96   private String smtpHost;
97   private String smtpUsername;
98   private String smtpPassword;
99   private String smtpProtocol;
100   private int smtpPort = -1;
101   private boolean smtpDebug = false;
102   private int bufferSize = 512;
103   private boolean locationInfo = false;
104   private boolean sendOnClose = false;
105
106   protected CyclicBuffer cb = new CyclicBuffer(bufferSize);
107   protected Message msg;
108
109   protected TriggeringEventEvaluator evaluator;
110
111
112
113   /**
114      The default constructor will instantiate the appender with a
115      {@link TriggeringEventEvaluator} that will trigger on events with
116      level ERROR or higher.*/
117   public
118   SMTPAppender() {
119     this(new DefaultEvaluator());
120   }
121
122
123   /**
124      Use <code>evaluator</code> passed as parameter as the {@link
125      TriggeringEventEvaluator} for this SMTPAppender.  */
126   public
127   SMTPAppender(TriggeringEventEvaluator evaluator) {
128     this.evaluator = evaluator;
129   }
130
131
132   /**
133      Activate the specified options, such as the smtp host, the
134      recipient, from, etc. */
135   public
136   void activateOptions() {
137     Session session = createSession();
138     msg = new MimeMessage(session);
139
140      try {
141         addressMessage(msg);
142         if(subject != null) {
143            try {
144                 msg.setSubject(MimeUtility.encodeText(subject, "UTF-8", null));
145            } catch(UnsupportedEncodingException ex) {
146                 LogLog.error("Unable to encode SMTP subject", ex);
147            }
148         }
149      } catch(MessagingException e) {
150        LogLog.error("Could not activate SMTPAppender options.", e );
151      }
152
153      if (evaluator instanceof OptionHandler) {
154          ((OptionHandler) evaluator).activateOptions();
155      }
156   }
157   
158   /**
159    *   Address message.
160    *   @param msg message, may not be null.
161    *   @throws MessagingException thrown if error addressing message. 
162    *   @since 1.2.14
163    */
164   protected void addressMessage(final Message msg) throws MessagingException {
165        if (from != null) {
166                         msg.setFrom(getAddress(from));
167        } else {
168                         msg.setFrom();
169            }
170
171       //Add ReplyTo addresses if defined.
172          if (replyTo != null && replyTo.length() > 0) {
173                msg.setReplyTo(parseAddress(replyTo));
174          }
175
176        if (to != null && to.length() > 0) {
177              msg.setRecipients(Message.RecipientType.TO, parseAddress(to));
178        }
179
180       //Add CC receipients if defined.
181           if (cc != null && cc.length() > 0) {
182                 msg.setRecipients(Message.RecipientType.CC, parseAddress(cc));
183           }
184
185       //Add BCC receipients if defined.
186           if (bcc != null && bcc.length() > 0) {
187                 msg.setRecipients(Message.RecipientType.BCC, parseAddress(bcc));
188           }
189   }
190   
191   /**
192    *  Create mail session.
193    *  @return mail session, may not be null.
194    *  @since 1.2.14
195    */
196   protected Session createSession() {
197     Properties props = null;
198     try {
199         props = new Properties (System.getProperties());
200     } catch(SecurityException ex) {
201         props = new Properties();
202     }
203
204     String prefix = "mail.smtp";
205     if (smtpProtocol != null) {
206         props.put("mail.transport.protocol", smtpProtocol);
207         prefix = "mail." + smtpProtocol;
208     }
209     if (smtpHost != null) {
210       props.put(prefix + ".host", smtpHost);
211     }
212     if (smtpPort > 0) {
213         props.put(prefix + ".port", String.valueOf(smtpPort));
214     }
215     
216     Authenticator auth = null;
217     if(smtpPassword != null && smtpUsername != null) {
218       props.put(prefix + ".auth", "true");
219       auth = new Authenticator() {
220         protected PasswordAuthentication getPasswordAuthentication() {
221           return new PasswordAuthentication(smtpUsername, smtpPassword);
222         }
223       };
224     }
225     Session session = Session.getInstance(props, auth);
226     if (smtpProtocol != null) {
227         session.setProtocolForAddress("rfc822", smtpProtocol);
228     }
229     if (smtpDebug) {
230         session.setDebug(smtpDebug);
231     }
232     return session;
233   }
234
235   /**
236      Perform SMTPAppender specific appending actions, mainly adding
237      the event to a cyclic buffer and checking if the event triggers
238      an e-mail to be sent. */
239   public
240   void append(LoggingEvent event) {
241
242     if(!checkEntryConditions()) {
243       return;
244     }
245
246     event.getThreadName();
247     event.getNDC();
248     event.getMDCCopy();
249     if(locationInfo) {
250       event.getLocationInformation();
251     }
252     event.getRenderedMessage();
253     event.getThrowableStrRep();
254     cb.add(event);
255     if(evaluator.isTriggeringEvent(event)) {
256       sendBuffer();
257     }
258   }
259
260  /**
261      This method determines if there is a sense in attempting to append.
262
263      <p>It checks whether there is a set output target and also if
264      there is a set layout. If these checks fail, then the boolean
265      value <code>false</code> is returned. */
266   protected
267   boolean checkEntryConditions() {
268     if(this.msg == null) {
269       errorHandler.error("Message object not configured.");
270       return false;
271     }
272
273     if(this.evaluator == null) {
274       errorHandler.error("No TriggeringEventEvaluator is set for appender ["+
275                          name+"].");
276       return false;
277     }
278
279
280     if(this.layout == null) {
281       errorHandler.error("No layout set for appender named ["+name+"].");
282       return false;
283     }
284     return true;
285   }
286
287
288   synchronized
289   public
290   void close() {
291     this.closed = true;
292     if (sendOnClose && cb.length() > 0) {
293         sendBuffer();
294     }
295   }
296
297   InternetAddress getAddress(String addressStr) {
298     try {
299       return new InternetAddress(addressStr);
300     } catch(AddressException e) {
301       errorHandler.error("Could not parse address ["+addressStr+"].", e,
302                          ErrorCode.ADDRESS_PARSE_FAILURE);
303       return null;
304     }
305   }
306
307   InternetAddress[] parseAddress(String addressStr) {
308     try {
309       return InternetAddress.parse(addressStr, true);
310     } catch(AddressException e) {
311       errorHandler.error("Could not parse address ["+addressStr+"].", e,
312                          ErrorCode.ADDRESS_PARSE_FAILURE);
313       return null;
314     }
315   }
316
317   /**
318      Returns value of the <b>To</b> option.
319    */
320   public
321   String getTo() {
322     return to;
323   }
324
325
326   /**
327      The <code>SMTPAppender</code> requires a {@link
328      org.apache.log4j.Layout layout}.  */
329   public
330   boolean requiresLayout() {
331     return true;
332   }
333
334   /**
335    * Layout body of email message.
336    * @since 1.2.16  
337    */
338   protected String formatBody() {
339           
340           // Note: this code already owns the monitor for this
341           // appender. This frees us from needing to synchronize on 'cb'.
342           
343       StringBuffer sbuf = new StringBuffer();
344       String t = layout.getHeader();
345       if(t != null) {
346         sbuf.append(t);
347     }
348       int len =  cb.length();
349       for(int i = 0; i < len; i++) {
350         //sbuf.append(MimeUtility.encodeText(layout.format(cb.get())));
351         LoggingEvent event = cb.get();
352         sbuf.append(layout.format(event));
353         if(layout.ignoresThrowable()) {
354           String[] s = event.getThrowableStrRep();
355           if (s != null) {
356             for(int j = 0; j < s.length; j++) {
357               sbuf.append(s[j]);
358               sbuf.append(Layout.LINE_SEP);
359             }
360           }
361         }
362       }
363       t = layout.getFooter();
364       if(t != null) {
365             sbuf.append(t);
366       }
367       
368       return sbuf.toString();
369   }
370   
371   /**
372      Send the contents of the cyclic buffer as an e-mail message.
373    */
374   protected
375   void sendBuffer() {
376
377     try {
378       String s = formatBody();
379       boolean allAscii = true;
380       for(int i = 0; i < s.length() && allAscii; i++) {
381           allAscii = s.charAt(i) <= 0x7F;
382       }
383       MimeBodyPart part;
384       if (allAscii) {
385           part = new MimeBodyPart();
386           part.setContent(s, layout.getContentType());
387       } else {
388           try {
389             ByteArrayOutputStream os = new ByteArrayOutputStream();
390             Writer writer = new OutputStreamWriter(
391                     MimeUtility.encode(os, "quoted-printable"), "UTF-8");
392             writer.write(s);
393             writer.close();
394             InternetHeaders headers = new InternetHeaders();
395             headers.setHeader("Content-Type", layout.getContentType() + "; charset=UTF-8");
396             headers.setHeader("Content-Transfer-Encoding", "quoted-printable");
397             part = new MimeBodyPart(headers, os.toByteArray());
398           } catch(Exception ex) {
399               StringBuffer sbuf = new StringBuffer(s);
400               for (int i = 0; i < sbuf.length(); i++) {
401                   if (sbuf.charAt(i) >= 0x80) {
402                       sbuf.setCharAt(i, '?');
403                   }
404               }
405               part = new MimeBodyPart();
406               part.setContent(sbuf.toString(), layout.getContentType());
407           }
408       }
409
410
411
412       Multipart mp = new MimeMultipart();
413       mp.addBodyPart(part);
414       msg.setContent(mp);
415
416       msg.setSentDate(new Date());
417       Transport.send(msg);
418     } catch(MessagingException e) {
419       LogLog.error("Error occured while sending e-mail notification.", e);
420     } catch(RuntimeException e) {
421       LogLog.error("Error occured while sending e-mail notification.", e);
422     }
423   }
424
425
426
427   /**
428      Returns value of the <b>EvaluatorClass</b> option.
429    */
430   public
431   String getEvaluatorClass() {
432     return evaluator == null ? null : evaluator.getClass().getName();
433   }
434
435   /**
436      Returns value of the <b>From</b> option.
437    */
438   public
439   String getFrom() {
440     return from;
441   }
442
443   /**
444      Get the reply addresses.
445      @return reply addresses as comma separated string, may be null.
446      @since 1.2.16
447    */
448   public
449   String getReplyTo() {
450     return replyTo;
451   }
452
453   /**
454      Returns value of the <b>Subject</b> option.
455    */
456   public
457   String getSubject() {
458     return subject;
459   }
460
461   /**
462      The <b>From</b> option takes a string value which should be a
463      e-mail address of the sender.
464    */
465   public
466   void setFrom(String from) {
467     this.from = from;
468   }
469
470   /**
471      Set the e-mail addresses to which replies should be directed.
472      @param addresses reply addresses as comma separated string, may be null.
473      @since 1.2.16
474    */
475   public
476   void setReplyTo(final String addresses) {
477     this.replyTo = addresses;
478   }
479
480
481   /**
482      The <b>Subject</b> option takes a string value which should be a
483      the subject of the e-mail message.
484    */
485   public
486   void setSubject(String subject) {
487     this.subject = subject;
488   }
489
490
491   /**
492      The <b>BufferSize</b> option takes a positive integer
493      representing the maximum number of logging events to collect in a
494      cyclic buffer. When the <code>BufferSize</code> is reached,
495      oldest events are deleted as new events are added to the
496      buffer. By default the size of the cyclic buffer is 512 events.
497    */
498   public
499   void setBufferSize(int bufferSize) {
500     this.bufferSize = bufferSize;
501     cb.resize(bufferSize);
502   }
503
504   /**
505      The <b>SMTPHost</b> option takes a string value which should be a
506      the host name of the SMTP server that will send the e-mail message.
507    */
508   public
509   void setSMTPHost(String smtpHost) {
510     this.smtpHost = smtpHost;
511   }
512
513   /**
514      Returns value of the <b>SMTPHost</b> option.
515    */
516   public
517   String getSMTPHost() {
518     return smtpHost;
519   }
520
521   /**
522      The <b>To</b> option takes a string value which should be a
523      comma separated list of e-mail address of the recipients.
524    */
525   public
526   void setTo(String to) {
527     this.to = to;
528   }
529
530
531
532   /**
533      Returns value of the <b>BufferSize</b> option.
534    */
535   public
536   int getBufferSize() {
537     return bufferSize;
538   }
539
540   /**
541      The <b>EvaluatorClass</b> option takes a string value
542      representing the name of the class implementing the {@link
543      TriggeringEventEvaluator} interface. A corresponding object will
544      be instantiated and assigned as the triggering event evaluator
545      for the SMTPAppender.
546    */
547   public
548   void setEvaluatorClass(String value) {
549       evaluator = (TriggeringEventEvaluator)
550                 OptionConverter.instantiateByClassName(value,
551                                            TriggeringEventEvaluator.class,
552                                                        evaluator);
553   }
554
555
556   /**
557      The <b>LocationInfo</b> option takes a boolean value. By
558      default, it is set to false which means there will be no effort
559      to extract the location information related to the event. As a
560      result, the layout that formats the events as they are sent out
561      in an e-mail is likely to place the wrong location information
562      (if present in the format).
563
564      <p>Location information extraction is comparatively very slow and
565      should be avoided unless performance is not a concern.
566    */
567   public
568   void setLocationInfo(boolean locationInfo) {
569     this.locationInfo = locationInfo;
570   }
571
572   /**
573      Returns value of the <b>LocationInfo</b> option.
574    */
575   public
576   boolean getLocationInfo() {
577     return locationInfo;
578   }
579   
580    /**
581       Set the cc recipient addresses.
582       @param addresses recipient addresses as comma separated string, may be null.
583       @since 1.2.14
584     */
585    public void setCc(final String addresses) {
586      this.cc = addresses;
587    }
588
589    /**
590       Get the cc recipient addresses.
591       @return recipient addresses as comma separated string, may be null.
592       @since 1.2.14
593     */
594     public String getCc() {
595      return cc;
596     }
597
598    /**
599       Set the bcc recipient addresses.
600       @param addresses recipient addresses as comma separated string, may be null.
601       @since 1.2.14
602     */
603    public void setBcc(final String addresses) {
604      this.bcc = addresses;
605    }
606
607    /**
608       Get the bcc recipient addresses.
609       @return recipient addresses as comma separated string, may be null.
610       @since 1.2.14
611     */
612     public String getBcc() {
613      return bcc;
614     }
615
616   /**
617    * The <b>SmtpPassword</b> option takes a string value which should be the password required to authenticate against
618    * the mail server.
619    * @param password password, may be null.
620    * @since 1.2.14
621    */
622   public void setSMTPPassword(final String password) {
623     this.smtpPassword = password;
624   }
625  
626   /**
627    * The <b>SmtpUsername</b> option takes a string value which should be the username required to authenticate against
628    * the mail server.
629    * @param username user name, may be null.
630    * @since 1.2.14
631    */
632   public void setSMTPUsername(final String username) {
633     this.smtpUsername = username;
634   }
635
636   /**
637    * Setting the <b>SmtpDebug</b> option to true will cause the mail session to log its server interaction to stdout.
638    * This can be useful when debuging the appender but should not be used during production because username and
639    * password information is included in the output.
640    * @param debug debug flag.
641    * @since 1.2.14
642    */
643   public void setSMTPDebug(final boolean debug) {
644     this.smtpDebug = debug;
645   }
646   
647   /**
648    * Get SMTP password.
649    * @return SMTP password, may be null.
650    * @since 1.2.14
651    */
652   public String getSMTPPassword() {
653     return smtpPassword;
654   }
655  
656   /**
657    * Get SMTP user name.
658    * @return SMTP user name, may be null.
659    * @since 1.2.14
660    */
661   public String getSMTPUsername() {
662     return smtpUsername;
663   }
664
665   /**
666    * Get SMTP debug.
667    * @return SMTP debug flag.
668    * @since 1.2.14
669    */
670   public boolean getSMTPDebug() {
671     return smtpDebug;
672   }
673
674     /**
675      * Sets triggering evaluator.
676      * @param trigger triggering event evaluator.
677      * @since 1.2.15
678      */
679   public final void setEvaluator(final TriggeringEventEvaluator trigger) {
680       if (trigger == null) {
681           throw new NullPointerException("trigger");
682       }
683       this.evaluator = trigger;
684   }
685
686     /**
687      * Get triggering evaluator.
688      * @return triggering event evaluator.
689      * @since 1.2.15
690      */
691   public final TriggeringEventEvaluator getEvaluator() {
692       return evaluator;
693   }
694
695   /** {@inheritDoc}
696    * @since 1.2.15 
697   */
698   public boolean parseUnrecognizedElement(final Element element,
699                                           final Properties props) throws Exception {
700       if ("triggeringPolicy".equals(element.getNodeName())) {
701           Object triggerPolicy =
702                   org.apache.log4j.xml.DOMConfigurator.parseElement(
703                           element, props, TriggeringEventEvaluator.class);
704           if (triggerPolicy instanceof TriggeringEventEvaluator) {
705               setEvaluator((TriggeringEventEvaluator) triggerPolicy);
706           }
707           return true;
708       }
709
710       return false;
711   }
712
713     /**
714      * Get transport protocol.
715      * Typically null or "smtps".
716      *
717      * @return transport protocol, may be null.
718      * @since 1.2.16
719      */
720   public final String getSMTPProtocol() {
721       return smtpProtocol;
722   }
723
724     /**
725      * Set transport protocol.
726      * Typically null or "smtps".
727      *
728      * @param val transport protocol, may be null.
729      * @since 1.2.16
730      */
731   public final void setSMTPProtocol(final String val) {
732       smtpProtocol = val;
733   }
734
735     /**
736      * Get port.
737      *
738      * @return port, negative values indicate use of default ports for protocol.
739      * @since 1.2.16
740      */
741   public final int getSMTPPort() {
742         return smtpPort;
743   }
744
745     /**
746      * Set port.
747      *
748      * @param val port, negative values indicate use of default ports for protocol.
749      * @since 1.2.16
750      */
751   public final void setSMTPPort(final int val) {
752         smtpPort = val;
753   }
754
755     /**
756      * Get sendOnClose.
757      *
758      * @return if true all buffered logging events will be sent when the appender is closed.
759      * @since 1.2.16
760      */
761   public final boolean getSendOnClose() {
762         return sendOnClose;
763   }
764
765     /**
766      * Set sendOnClose.
767      *
768      * @param val if true all buffered logging events will be sent when appender is closed.
769      * @since 1.2.16
770      */
771   public final void setSendOnClose(final boolean val) {
772         sendOnClose = val;
773   }
774
775 }
776
777 class DefaultEvaluator implements TriggeringEventEvaluator {
778   /**
779      Is this <code>event</code> the e-mail triggering event?
780
781      <p>This method returns <code>true</code>, if the event level
782      has ERROR level or higher. Otherwise it returns
783      <code>false</code>. */
784   public
785   boolean isTriggeringEvent(LoggingEvent event) {
786     return event.getLevel().isGreaterOrEqual(Level.ERROR);
787   }
788 }