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