View Javadoc

1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * 
4    * Copyright (C) 1999-2006, QOS.ch
5    * 
6    * This library is free software, you can redistribute it and/or modify it under
7    * the terms of the GNU Lesser General Public License as published by the Free
8    * Software Foundation.
9    */
10  
11  package ch.qos.logback.core.net;
12  
13  import java.util.ArrayList;
14  import java.util.Date;
15  import java.util.List;
16  import java.util.Properties;
17  
18  import javax.mail.Message;
19  import javax.mail.MessagingException;
20  import javax.mail.Multipart;
21  import javax.mail.Session;
22  import javax.mail.Transport;
23  import javax.mail.internet.AddressException;
24  import javax.mail.internet.InternetAddress;
25  import javax.mail.internet.MimeBodyPart;
26  import javax.mail.internet.MimeMessage;
27  import javax.mail.internet.MimeMultipart;
28  
29  import ch.qos.logback.core.AppenderBase;
30  import ch.qos.logback.core.Layout;
31  import ch.qos.logback.core.boolex.EvaluationException;
32  import ch.qos.logback.core.boolex.EventEvaluator;
33  import ch.qos.logback.core.util.ContentTypeUtil;
34  import ch.qos.logback.core.util.OptionHelper;
35  
36  // Contributors:
37  // Andrey Rybin charset encoding support http://jira.qos.ch/browse/LBCORE-69
38  
39  /**
40   * An abstract class that provides support for sending events to an email
41   * address.
42   * 
43   * <p>See http://logback.qos.ch/manual/appenders.html#SMTPAppender for further
44   * documentation.
45   * 
46   * @author Ceki G&uuml;lc&uuml;
47   * @author S&eacute;bastien Pennec
48   */
49  public abstract class SMTPAppenderBase<E> extends AppenderBase<E> {
50  
51    protected Layout<E> subjectLayout;
52  
53    private List<String> to = new ArrayList<String>();
54    private String from;
55    private String subjectStr = null;
56    private String smtpHost;
57    private int smtpPort = 25;
58    private boolean starttls = false;
59    private boolean ssl = false;
60  
61    String username;
62    String password;
63  
64    private String charsetEncoding = "UTF-8";
65  
66    protected MimeMessage mimeMsg;
67  
68    protected EventEvaluator<E> eventEvaluator;
69  
70    /**
71     * return a layout for the subjet string as appropriate for the module. If the
72     * subjectStr parameter is null, then a default value for subjectStr should be
73     * used.
74     * 
75     * @param subjectStr
76     * 
77     * @return a layout as appropriate for the module
78     */
79    abstract protected Layout<E> makeSubjectLayout(String subjectStr);
80  
81    /**
82     * Start the appender
83     */
84    public void start() {
85      Properties props = new Properties(OptionHelper.getSystemProperties());
86      if (smtpHost != null) {
87        props.put("mail.smtp.host", smtpHost);
88      }
89      props.put("mail.smtp.port", Integer.toString(smtpPort));
90  
91      LoginAuthenticator loginAuthenticator = null;
92  
93      if (username != null) {
94        loginAuthenticator = new LoginAuthenticator(username, password);
95        props.put("mail.smtp.auth", "true");
96      }
97  
98      if (isSTARTTLS() && isSSL()) {
99        addError("Both SSL and StartTLS cannot be enabled simultaneously");
100     } else {
101       if (isSTARTTLS()) {
102         props.setProperty("mail.smtp.auth", "true");
103         props.put("mail.smtp.starttls.enable", "true");
104       }
105       if (isSSL()) {
106         String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
107         props.put("mail.smtp.socketFactory.port", Integer.toString(smtpPort));
108         props.put("mail.smtp.socketFactory.class", SSL_FACTORY);
109         props.put("mail.smtp.socketFactory.fallback", "true");
110       }
111     }
112 
113     // props.put("mail.debug", "true");
114 
115     Session session = Session.getInstance(props, loginAuthenticator);
116     mimeMsg = new MimeMessage(session);
117 
118     try {
119       if (from != null) {
120         mimeMsg.setFrom(getAddress(from));
121       } else {
122         mimeMsg.setFrom();
123       }
124 
125       mimeMsg.setRecipients(Message.RecipientType.TO, parseAddress(to));
126 
127       subjectLayout = makeSubjectLayout(subjectStr);
128 
129       started = true;
130 
131     } catch (MessagingException e) {
132       addError("Could not activate SMTPAppender options.", e);
133     }
134   }
135 
136   /**
137    * Perform SMTPAppender specific appending actions, delegating some of them to
138    * a subclass and checking if the event triggers an e-mail to be sent.
139    */
140   protected void append(E eventObject) {
141 
142     if (!checkEntryConditions()) {
143       return;
144     }
145 
146     subAppend(eventObject);
147 
148     try {
149       if (eventEvaluator.evaluate(eventObject)) {
150         sendBuffer(eventObject);
151       }
152     } catch (EvaluationException ex) {
153       addError("SMTPAppender's EventEvaluator threw an Exception" + ex);
154     }
155   }
156 
157   abstract protected void subAppend(E eventObject);
158 
159   /**
160    * This method determines if there is a sense in attempting to append.
161    * 
162    * <p> It checks whether there is a set output target and also if there is a
163    * set layout. If these checks fail, then the boolean value <code>false</code>
164    * is returned.
165    */
166   public boolean checkEntryConditions() {
167     if (!this.started) {
168       addError("Attempting to append to a non-started appender: "
169           + this.getName());
170       return false;
171     }
172 
173     if (this.mimeMsg == null) {
174       addError("Message object not configured.");
175       return false;
176     }
177 
178     if (this.eventEvaluator == null) {
179       addError("No EventEvaluator is set for appender [" + name + "].");
180       return false;
181     }
182 
183     if (this.layout == null) {
184       addError("No layout set for appender named ["
185           + name
186           + "]. For more information, please visit http://logback.qos.ch/codes.html#smtp_no_layout");
187       return false;
188     }
189     return true;
190   }
191 
192   synchronized public void stop() {
193     this.started = false;
194   }
195 
196   InternetAddress getAddress(String addressStr) {
197     try {
198       return new InternetAddress(addressStr);
199     } catch (AddressException e) {
200       addError("Could not parse address [" + addressStr + "].", e);
201       return null;
202     }
203   }
204 
205   InternetAddress[] parseAddress(List<String> addressList) {
206 
207     InternetAddress[] iaArray = new InternetAddress[addressList.size()];
208 
209     for (int i = 0; i < addressList.size(); i++) {
210       try {
211         InternetAddress[] tmp = InternetAddress.parse(addressList.get(i), true);
212         // one <To> element should contain one email address
213         iaArray[i] = tmp[0];
214       } catch (AddressException e) {
215         addError("Could not parse address [" + addressList.get(i) + "].", e);
216         return null;
217       }
218     }
219 
220     return iaArray;
221   }
222 
223   /**
224    * Returns value of the <b>To</b> option.
225    */
226   public List<String> getTo() {
227     return to;
228   }
229 
230   /**
231    * Send the contents of the cyclic buffer as an e-mail message.
232    */
233   protected void sendBuffer(E lastEventObject) {
234 
235     // Note: this code already owns the monitor for this
236     // appender. This frees us from needing to synchronize on 'cb'.
237     try {
238       MimeBodyPart part = new MimeBodyPart();
239 
240       StringBuffer sbuf = new StringBuffer();
241 
242       String header = layout.getFileHeader();
243       if (header != null) {
244         sbuf.append(header);
245       }
246       String presentationHeader = layout.getPresentationHeader();
247       if (presentationHeader != null) {
248         sbuf.append(presentationHeader);
249       }
250       fillBuffer(sbuf);
251       String presentationFooter = layout.getPresentationFooter();
252       if (presentationFooter != null) {
253         sbuf.append(presentationFooter);
254       }
255       String footer = layout.getFileFooter();
256       if (footer != null) {
257         sbuf.append(footer);
258       }
259 
260       if (subjectLayout != null) {
261         mimeMsg.setSubject(subjectLayout.doLayout(lastEventObject),
262             charsetEncoding);
263       }
264 
265       String contentType = layout.getContentType();
266 
267       if (ContentTypeUtil.isTextual(contentType)) {
268         part.setText(sbuf.toString(), charsetEncoding, ContentTypeUtil
269             .getSubType(contentType));
270       } else {
271         part.setContent(sbuf.toString(), layout.getContentType());
272       }
273 
274       Multipart mp = new MimeMultipart();
275       mp.addBodyPart(part);
276       mimeMsg.setContent(mp);
277 
278       mimeMsg.setSentDate(new Date());
279       Transport.send(mimeMsg);
280     } catch (Exception e) {
281       addError("Error occured while sending e-mail notification.", e);
282     }
283   }
284 
285   abstract protected void fillBuffer(StringBuffer sbuf);
286 
287   /**
288    * Returns value of the <b>From</b> option.
289    */
290   public String getFrom() {
291     return from;
292   }
293 
294   /**
295    * Returns value of the <b>Subject</b> option.
296    */
297   public String getSubject() {
298     return subjectStr;
299   }
300 
301   /**
302    * The <b>From</b> option takes a string value which should be a e-mail
303    * address of the sender.
304    */
305   public void setFrom(String from) {
306     this.from = from;
307   }
308 
309   /**
310    * The <b>Subject</b> option takes a string value which should be a the
311    * subject of the e-mail message.
312    */
313   public void setSubject(String subject) {
314     this.subjectStr = subject;
315   }
316 
317   /**
318    * The <b>SMTPHost</b> option takes a string value which should be a the host
319    * name of the SMTP server that will send the e-mail message.
320    */
321   public void setSMTPHost(String smtpHost) {
322     this.smtpHost = smtpHost;
323   }
324 
325   /**
326    * Returns value of the <b>SMTPHost</b> option.
327    */
328   public String getSMTPHost() {
329     return smtpHost;
330   }
331 
332   /**
333    * The port where the SMTP server is running. Default value is 25.
334    * 
335    * @param port
336    */
337   public void setSMTPPort(int port) {
338     this.smtpPort = port;
339   }
340 
341   /**
342    * @see #setSMTPPort(int)
343    * @return
344    */
345   public int getSMTPPort() {
346     return smtpPort;
347   }
348 
349   /**
350    * The <b>To</b> option takes a string value which should be an e-mail
351    * address of one of the recipients.
352    */
353   public void addTo(String to) {
354     this.to.add(to);
355   }
356 
357   // for testing purpose only
358   public Message getMessage() {
359     return mimeMsg;
360   }
361 
362   // for testing purpose only
363   public void setMessage(MimeMessage msg) {
364     this.mimeMsg = msg;
365   }
366 
367   public boolean isSTARTTLS() {
368     return starttls;
369   }
370 
371   public void setSTARTTLS(boolean startTLS) {
372     this.starttls = startTLS;
373   }
374 
375   public boolean isSSL() {
376     return ssl;
377   }
378 
379   public void setSSL(boolean ssl) {
380     this.ssl = ssl;
381   }
382 
383   /**
384    * The <b>EventEvaluator</b> option takes a string value representing the
385    * name of the class implementing the {@link EventEvaluators} interface. A
386    * corresponding object will be instantiated and assigned as the event
387    * evaluator for the SMTPAppender.
388    */
389   public void setEvaluator(EventEvaluator<E> eventEvaluator) {
390     this.eventEvaluator = eventEvaluator;
391   }
392 
393   public String getUsername() {
394     return username;
395   }
396 
397   public void setUsername(String username) {
398     this.username = username;
399   }
400 
401   public String getPassword() {
402     return password;
403   }
404 
405   public void setPassword(String password) {
406     this.password = password;
407   }
408 
409   /**
410    * @see #setCharsetEncoding(String)
411    * @return the charset encoding value
412    */
413   String getCharsetEncoding() {
414     return charsetEncoding;
415   }
416 
417   /**
418    * Set the character set encoding of the outgoing email messages. The default
419    * encoding is "UTF-8" which usually works well for most purposes.
420    * 
421    * @param charsetEncoding
422    */
423   void setCharsetEncoding(String charsetEncoding) {
424     this.charsetEncoding = charsetEncoding;
425   }
426 
427 }