View Javadoc
1   package org.apache.maven.plugin.announcement;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileNotFoundException;
25  import java.io.IOException;
26  import java.io.InputStreamReader;
27  import java.io.UnsupportedEncodingException;
28  import java.util.List;
29  
30  import javax.mail.internet.AddressException;
31  import javax.mail.internet.InternetAddress;
32  
33  import org.apache.maven.model.Developer;
34  import org.apache.maven.plugin.MojoExecutionException;
35  import org.apache.maven.plugin.announcement.mailsender.ProjectJavamailMailSender;
36  import org.apache.maven.plugins.annotations.Component;
37  import org.apache.maven.plugins.annotations.Execute;
38  import org.apache.maven.plugins.annotations.Mojo;
39  import org.apache.maven.plugins.annotations.Parameter;
40  import org.apache.maven.project.MavenProject;
41  import org.codehaus.plexus.logging.Logger;
42  import org.codehaus.plexus.logging.console.ConsoleLogger;
43  import org.codehaus.plexus.mailsender.MailMessage;
44  import org.codehaus.plexus.mailsender.MailSenderException;
45  import org.codehaus.plexus.util.IOUtil;
46  import org.codehaus.plexus.util.ReaderFactory;
47  import org.codehaus.plexus.util.StringUtils;
48  
49  /**
50   * Goal which sends an announcement through email.
51   *
52   * @author aramirez@exist.com
53   * @version $Id: AnnouncementMailMojo.java 1579078 2014-03-18 22:44:41Z dennisl $
54   * @since 2.0-beta-2
55   */
56  @Mojo( name = "announcement-mail", threadSafe = true )
57  @Execute( goal = "announcement-generate" )
58  public class AnnouncementMailMojo
59      extends AbstractAnnouncementMojo
60  {
61      //=========================================
62      // announcement-mail goal fields
63      //=========================================
64  
65      /**
66       * Possible senders.
67       */
68      @Parameter( property = "project.developers", required = true, readonly = true )
69      private List from;
70  
71      /**
72       * The id of the developer sending the announcement mail. Only used if the <tt>mailSender</tt>
73       * attribute is not set. In this case, this should match the id of one of the developers in
74       * the pom. If a matching developer is not found, then the first developer in the pom will be
75       * used.
76       */
77      @Parameter( property = "changes.fromDeveloperId" )
78      private String fromDeveloperId;
79  
80      /**
81       * Mail content type to use.
82       *
83       * @since 2.1
84       */
85      @Parameter( defaultValue = "text/plain", required = true )
86      private String mailContentType;
87  
88      /**
89       * Defines the sender of the announcement email. This takes precedence over the list
90       * of developers specified in the POM.
91       * if the sender is not a member of the development team. Note that since this is a bean type,
92       * you cannot specify it from command level with <pre>-D</pre>. Use
93       * <pre>-Dchanges.sender='Your Name &lt;you@domain>'</pre> instead.
94       */
95      @Parameter( property = "changes.mailSender" )
96      private MailSender mailSender;
97  
98      /**
99       * Defines the sender of the announcement. This takes precedence over both ${changes.mailSender}
100      * and the list of developers in the POM.
101      * <p/>
102      * This parameter parses an email address in standard RFC822 format, e.g.
103      * <pre>-Dchanges.sender='Your Name &lt;you@domain>'</pre>.
104      *
105      * @since 2.7
106      */
107     @Parameter( property = "changes.sender" )
108     private String senderString;
109 
110     /**
111      * The password used to send the email.
112      */
113     @Parameter( property = "changes.password" )
114     private String password;
115 
116     /**
117      */
118     @Component
119     private MavenProject project;
120 
121     /**
122      * Smtp Server.
123      */
124     @Parameter( property = "changes.smtpHost", required = true )
125     private String smtpHost;
126 
127     /**
128      * Port.
129      */
130     @Parameter( property = "changes.smtpPort", defaultValue = "25", required = true )
131     private int smtpPort;
132 
133     /**
134      * If the email should be sent in SSL mode.
135      */
136     @Parameter( property = "changes.sslMode", defaultValue = "false" )
137     private boolean sslMode;
138 
139     /**
140      * If the option startTls should be used.
141      *
142      * @since 2.10
143      */
144     @Parameter( property = "changes.startTls", defaultValue = "false" )
145     private boolean startTls;
146 
147     /**
148      * Subject for the email.
149      */
150     @Parameter( property = "changes.subject",
151                 defaultValue = "[ANNOUNCEMENT] - ${project.name} ${project.version} released", required = true )
152     private String subject;
153 
154     /**
155      * The file that contains the generated announcement.
156      *
157      * @since 2.10
158      */
159     @Parameter( property = "changes.announcementFile", defaultValue = "announcement.vm", required = true )
160     private String announcementFile;
161 
162     /**
163      * Directory where the generated announcement file exists.
164      *
165      * @since 2.10
166      */
167     @Parameter( defaultValue = "${project.build.directory}/announcement", required = true )
168     private File announcementDirectory;
169 
170     /**
171      * The encoding used in the announcement template.
172      *
173      * @since 2.10
174      */
175     @Parameter( property = "changes.templateEncoding", defaultValue = "${project.build.sourceEncoding}" )
176     private String templateEncoding;
177 
178     /**
179      * Directory which contains the template for announcement email.
180      *
181      * @deprecated Starting with version 2.10 this parameter is no longer used. You must use {@link #announcementDirectory} instead.
182      */
183     @Parameter
184     private File templateOutputDirectory;
185 
186     /**
187      * Recipient email address.
188      */
189     @Parameter( required = true )
190     private List toAddresses;
191 
192     /**
193      * Recipient cc email address.
194      *
195      * @since 2.5
196      */
197     @Parameter
198     private List ccAddresses;
199 
200     /**
201      * Recipient bcc email address.
202      *
203      * @since 2.5
204      */
205     @Parameter
206     private List bccAddresses;
207 
208     /**
209      * The username used to send the email.
210      */
211     @Parameter( property = "changes.username" )
212     private String username;
213 
214     private ProjectJavamailMailSender mailer = new ProjectJavamailMailSender();
215 
216     public void execute()
217         throws MojoExecutionException
218     {
219         // Fail build fast if it is using deprecated parameters
220         if ( templateOutputDirectory != null )
221         {
222             throw new MojoExecutionException( "You are using the old parameter 'templateOutputDirectory'. You must use 'announcementDirectory' instead." );
223         }
224 
225         // Run only at the execution root
226         if ( runOnlyAtExecutionRoot && !isThisTheExecutionRoot() )
227         {
228             getLog().info( "Skipping the announcement mail in this project because it's not the Execution Root" );
229         }
230         else
231         {
232             File file = new File( announcementDirectory, announcementFile );
233 
234             ConsoleLogger logger = new ConsoleLogger( Logger.LEVEL_INFO, "base" );
235 
236             if ( getLog().isDebugEnabled() )
237             {
238                 logger.setThreshold( Logger.LEVEL_DEBUG );
239             }
240 
241             mailer.enableLogging( logger );
242 
243             mailer.setSmtpHost( getSmtpHost() );
244 
245             mailer.setSmtpPort( getSmtpPort() );
246 
247             mailer.setSslMode( sslMode, startTls );
248 
249             if ( username != null )
250             {
251                 mailer.setUsername( username );
252             }
253 
254             if ( password != null )
255             {
256                 mailer.setPassword( password );
257             }
258 
259             mailer.initialize();
260 
261             if ( getLog().isDebugEnabled() )
262             {
263                 getLog().debug( "fromDeveloperId: " + getFromDeveloperId() );
264             }
265 
266             if ( file.isFile() )
267             {
268                 getLog().info( "Connecting to Host: " + getSmtpHost() + ":" + getSmtpPort() );
269 
270                 sendMessage();
271             }
272             else
273             {
274                 throw new MojoExecutionException( "Announcement file " + file + " not found..." );
275             }
276         }
277     }
278 
279     /**
280      * Send the email.
281      *
282      * @throws MojoExecutionException if the mail could not be sent
283      */
284     protected void sendMessage()
285         throws MojoExecutionException
286     {
287         File file = new File( announcementDirectory, announcementFile );
288         String email = "";
289         final MailSender ms = getActualMailSender();
290         final String fromName = ms.getName();
291         final String fromAddress = ms.getEmail();
292         if ( fromAddress == null || fromAddress.equals( "" ) )
293         {
294             throw new MojoExecutionException( "Invalid mail sender: name and email is mandatory (" + ms + ")." );
295         }
296         getLog().info( "Using this sender for email announcement: " + fromAddress + " < " + fromName + " > " );
297         try
298         {
299             MailMessage mailMsg = new MailMessage();
300             mailMsg.setSubject( getSubject() );
301             mailMsg.setContent( readAnnouncement( file ) );
302             mailMsg.setContentType( this.mailContentType );
303             mailMsg.setFrom( fromAddress, fromName );
304 
305             for ( Object o1 : getToAddresses() )
306             {
307                 email = o1.toString();
308                 getLog().info( "Sending mail to " + email + "..." );
309                 mailMsg.addTo( email, "" );
310             }
311 
312             if ( getCcAddresses() != null )
313             {
314                 for ( Object o : getCcAddresses() )
315                 {
316                     email = o.toString();
317                     getLog().info( "Sending cc mail to " + email + "..." );
318                     mailMsg.addCc( email, "" );
319                 }
320             }
321 
322             if ( getBccAddresses() != null )
323             {
324                 for ( Object o : getBccAddresses() )
325                 {
326                     email = o.toString();
327                     getLog().info( "Sending bcc mail to " + email + "..." );
328                     mailMsg.addBcc( email, "" );
329                 }
330             }
331 
332             mailer.send( mailMsg );
333             getLog().info( "Sent..." );
334         }
335         catch ( MailSenderException e )
336         {
337             throw new MojoExecutionException( "Failed to send email < " + email + " >", e );
338         }
339     }
340 
341     /**
342      * Read the content of the generated announcement file.
343      *
344      * @param file the file to be read
345      * @return Return the announcement text
346      * @throws MojoExecutionException if the file could not be found, or if the encoding is unsupported
347      */
348     protected String readAnnouncement( File file )
349         throws MojoExecutionException
350     {
351         InputStreamReader reader = null;
352         FileInputStream inputStream = null;
353         try
354         {
355             inputStream = new FileInputStream( file );
356 
357             if ( StringUtils.isEmpty( templateEncoding ) )
358             {
359                 templateEncoding = ReaderFactory.FILE_ENCODING;
360                 getLog().warn( "File encoding has not been set, using platform encoding '" + templateEncoding
361                                    + "', i.e. build is platform dependent!" );
362             }
363 
364             reader = new InputStreamReader( inputStream, templateEncoding );
365             return IOUtil.toString( reader );
366         }
367         catch ( FileNotFoundException fnfe )
368         {
369             throw new MojoExecutionException( "File not found. " + file );
370         }
371         catch ( UnsupportedEncodingException uee )
372         {
373             throw new MojoExecutionException( "Unsupported encoding: '" + templateEncoding + "'" );
374         }
375         catch ( IOException ioe )
376         {
377             throw new MojoExecutionException( "Failed to read the announcement file.", ioe );
378         }
379         finally
380         {
381             IOUtil.close( inputStream );
382             IOUtil.close( reader );
383         }
384     }
385 
386     /**
387      * Returns the identify of the mail sender according to the plugin's configuration:
388      * <ul>
389      * <li>if the <tt>mailSender</tt> parameter is set, it is returned</li>
390      * <li>if no <tt>fromDeveloperId</tt> is set, the first developer in the list is returned</li>
391      * <li>if a <tt>fromDeveloperId</tt> is set, the developer with that id is returned</li>
392      * <li>if the developers list is empty or if the specified id does not exist, an exception is thrown</li>
393      * </ul>
394      *
395      * @return the mail sender to use
396      * @throws MojoExecutionException if the mail sender could not be retrieved
397      */
398     protected MailSender getActualMailSender()
399         throws MojoExecutionException
400     {
401         if ( senderString != null )
402         {
403             try
404             {
405                 InternetAddress ia = new InternetAddress( senderString, true );
406                 return new MailSender( ia.getPersonal(), ia.getAddress() );
407             }
408             catch ( AddressException e )
409             {
410                 throw new MojoExecutionException( "Invalid value for change.sender: ", e );
411             }
412         }
413         if ( mailSender != null && mailSender.getEmail() != null )
414         {
415             return mailSender;
416         }
417         else if ( from == null || from.isEmpty() )
418         {
419             throw new MojoExecutionException(
420                 "The <developers> section in your pom should not be empty. Add a <developer> entry or set the "
421                     + "mailSender parameter." );
422         }
423         else if ( fromDeveloperId == null )
424         {
425             final Developer dev = (Developer) from.get( 0 );
426             return new MailSender( dev.getName(), dev.getEmail() );
427         }
428         else
429         {
430             for ( Object aFrom : from )
431             {
432                 Developer developer = (Developer) aFrom;
433 
434                 if ( fromDeveloperId.equals( developer.getId() ) )
435                 {
436                     return new MailSender( developer.getName(), developer.getEmail() );
437                 }
438             }
439             throw new MojoExecutionException(
440                 "Missing developer with id '" + fromDeveloperId + "' in the <developers> section in your pom." );
441         }
442     }
443 
444     //================================
445     // announcement-mail accessors
446     //================================
447 
448     public List getBccAddresses()
449     {
450         return bccAddresses;
451     }
452 
453     public void setBccAddresses( List bccAddresses )
454     {
455         this.bccAddresses = bccAddresses;
456     }
457 
458     public List getCcAddresses()
459     {
460         return ccAddresses;
461     }
462 
463     public void setCcAddresses( List ccAddresses )
464     {
465         this.ccAddresses = ccAddresses;
466     }
467 
468     public List getFrom()
469     {
470         return from;
471     }
472 
473     public void setFrom( List from )
474     {
475         this.from = from;
476     }
477 
478     public String getFromDeveloperId()
479     {
480         return fromDeveloperId;
481     }
482 
483     public void setFromDeveloperId( String fromDeveloperId )
484     {
485         this.fromDeveloperId = fromDeveloperId;
486     }
487 
488     public MailSender getMailSender()
489     {
490         return mailSender;
491     }
492 
493     public void setMailSender( MailSender mailSender )
494     {
495         this.mailSender = mailSender;
496     }
497 
498     public String getPassword()
499     {
500         return password;
501     }
502 
503     public void setPassword( String password )
504     {
505         this.password = password;
506     }
507 
508     public MavenProject getProject()
509     {
510         return project;
511     }
512 
513     public void setProject( MavenProject project )
514     {
515         this.project = project;
516     }
517 
518     public String getSmtpHost()
519     {
520         return smtpHost;
521     }
522 
523     public void setSmtpHost( String smtpHost )
524     {
525         this.smtpHost = smtpHost;
526     }
527 
528     public int getSmtpPort()
529     {
530         return smtpPort;
531     }
532 
533     public void setSmtpPort( int smtpPort )
534     {
535         this.smtpPort = smtpPort;
536     }
537 
538     public boolean isSslMode()
539     {
540         return sslMode;
541     }
542 
543     public void setSslMode( boolean sslMode )
544     {
545         this.sslMode = sslMode;
546     }
547 
548     public boolean isStartTls()
549     {
550         return startTls;
551     }
552 
553     public void setStartTls( boolean startTls )
554     {
555         this.startTls = startTls;
556     }
557 
558     public String getSubject()
559     {
560         return subject;
561     }
562 
563     public void setSubject( String subject )
564     {
565         this.subject = subject;
566     }
567 
568     public String getAnnouncementFile()
569     {
570         return announcementFile;
571     }
572 
573     public void setAnnouncementFile( String announcementFile )
574     {
575         this.announcementFile = announcementFile;
576     }
577 
578     public File getAnnouncementDirectory()
579     {
580         return announcementDirectory;
581     }
582 
583     public void setAnnouncementDirectory( File announcementDirectory )
584     {
585         this.announcementDirectory = announcementDirectory;
586     }
587 
588     public List getToAddresses()
589     {
590         return toAddresses;
591     }
592 
593     public void setToAddresses( List toAddresses )
594     {
595         this.toAddresses = toAddresses;
596     }
597 
598     public String getUsername()
599     {
600         return username;
601     }
602 
603     public void setUsername( String username )
604     {
605         this.username = username;
606     }
607 }