View Javadoc

1   package org.apache.maven.continuum.notification.mail;
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.StringWriter;
23  import java.io.UnsupportedEncodingException;
24  import java.net.InetAddress;
25  import java.net.UnknownHostException;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Date;
29  import java.util.HashMap;
30  import java.util.HashSet;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  
35  import javax.mail.Message;
36  import javax.mail.MessagingException;
37  import javax.mail.internet.AddressException;
38  import javax.mail.internet.InternetAddress;
39  import javax.mail.internet.MimeMessage;
40  
41  import org.apache.continuum.model.project.ProjectScmRoot;
42  import org.apache.maven.continuum.Continuum;
43  import org.apache.maven.continuum.configuration.ConfigurationService;
44  import org.apache.maven.continuum.execution.ExecutorConfigurator;
45  import org.apache.maven.continuum.execution.ant.AntBuildExecutor;
46  import org.apache.maven.continuum.execution.maven.m1.MavenOneBuildExecutor;
47  import org.apache.maven.continuum.execution.maven.m2.MavenTwoBuildExecutor;
48  import org.apache.maven.continuum.installation.InstallationException;
49  import org.apache.maven.continuum.installation.InstallationService;
50  import org.apache.maven.continuum.model.project.BuildDefinition;
51  import org.apache.maven.continuum.model.project.BuildResult;
52  import org.apache.maven.continuum.model.project.Project;
53  import org.apache.maven.continuum.model.project.ProjectDeveloper;
54  import org.apache.maven.continuum.model.project.ProjectGroup;
55  import org.apache.maven.continuum.model.project.ProjectNotifier;
56  import org.apache.maven.continuum.model.scm.ChangeSet;
57  import org.apache.maven.continuum.model.scm.ScmResult;
58  import org.apache.maven.continuum.model.system.Installation;
59  import org.apache.maven.continuum.model.system.Profile;
60  import org.apache.maven.continuum.notification.AbstractContinuumNotifier;
61  import org.apache.maven.continuum.notification.ContinuumNotificationDispatcher;
62  import org.apache.maven.continuum.notification.MessageContext;
63  import org.apache.maven.continuum.notification.NotificationException;
64  import org.apache.maven.continuum.project.ContinuumProjectState;
65  import org.apache.maven.continuum.reports.surefire.ReportTestResult;
66  import org.apache.maven.continuum.reports.surefire.ReportTestSuiteGenerator;
67  import org.apache.velocity.VelocityContext;
68  import org.apache.velocity.exception.ResourceNotFoundException;
69  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
70  import org.codehaus.plexus.util.StringUtils;
71  import org.codehaus.plexus.velocity.VelocityComponent;
72  import org.slf4j.Logger;
73  import org.slf4j.LoggerFactory;
74  import org.springframework.mail.javamail.JavaMailSender;
75  
76  /**
77   * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
78   * @version $Id: MailContinuumNotifier.java 812315 2009-09-08 01:32:28Z ctan $
79   */
80  public class MailContinuumNotifier
81      extends AbstractContinuumNotifier
82      implements Initializable
83  {
84      private static final Logger log = LoggerFactory.getLogger( MailContinuumNotifier.class );
85  
86      // ----------------------------------------------------------------------
87      // Requirements
88      // ----------------------------------------------------------------------
89  
90      /**
91       * @plexus.requirement
92       */
93      private VelocityComponent velocity;
94  
95      /**
96       * @plexus.requirement
97       */
98      private ConfigurationService configurationService;
99  
100     /**
101      * @plexus.requirement
102      */
103     private Continuum continuum;
104 
105     /**
106      * @plexus.requirement
107      */
108     private JavaMailSender javaMailSender;
109 
110     /**
111      * @plexus.requirement
112      */
113     private ReportTestSuiteGenerator reportTestSuiteGenerator;
114 
115     // ----------------------------------------------------------------------
116     // Configuration
117     // ----------------------------------------------------------------------
118     /**
119      * @plexus.configuration
120      */
121     private String fromMailbox;
122 
123     /**
124      * @plexus.configuration
125      */
126     private String fromName;
127 
128     /**
129      * @plexus.configuration
130      */
131     private String toOverride;
132 
133     /**
134      * @plexus.configuration
135      */
136     private String timestampFormat;
137 
138     /**
139      * @plexus.configuration
140      */
141     private boolean includeBuildSummary = true;
142 
143     /**
144      * @plexus.configuration
145      */
146     private boolean includeTestSummary = true;
147 
148     /**
149      * @plexus.configuration
150      */
151     private boolean includeBuildOutput = false;
152 
153     /**
154      * Customizable mail subject.  Use any combination of literal text, project or build attributes.
155      * Examples:
156      * "[continuum] BUILD ${state}: ${project.groupId} ${project.name}" results in "[continuum] BUILD SUCCESSFUL: foo.bar Hello World"
157      * "[continuum] BUILD ${state}: ${project.name} ${project.scmTag}" results in "[continuum] BUILD SUCCESSFUL: Hello World Branch001"
158      * "[continuum] BUILD ${state}: ${project.name} ${build.durationTime}" results in "[continuum] BUILD SUCCESSFUL: Hello World 2 sec"
159      * "[continuum] BUILD ${state}: ${project.name}, Build Def - ${build.buildDefinition.description}" results in "[continuum] BUILD SUCCESSFUL: Hello World, Build Def - Nightly Test Build"
160      *
161      * @plexus.configuration
162      */
163     private String buildSubjectFormat = "[continuum] BUILD ${state}: ${project.groupId} ${project.name}";
164 
165     /**
166      * Customizable mail subject
167      *
168      * @plexus.configuration
169      */
170     private String prepareBuildSubjectFormat = "[continuum] PREPARE BUILD ${state]: ${projectScmRoot.projectGroup.name}";
171 
172     // ----------------------------------------------------------------------
173     //
174     // ----------------------------------------------------------------------
175 
176     private String buildHost;
177 
178     private FormatterTool formatterTool;
179 
180     // ----------------------------------------------------------------------
181     //
182     // ----------------------------------------------------------------------
183 
184     private static final String FALLBACK_FROM_MAILBOX = "continuum@localhost";
185 
186     // ----------------------------------------------------------------------
187     // Component Lifecycle
188     // ----------------------------------------------------------------------
189 
190     public void initialize()
191     {
192         try
193         {
194             InetAddress address = InetAddress.getLocalHost();
195 
196             buildHost = StringUtils.clean( address.getHostName() );
197 
198             if ( buildHost == null )
199             {
200                 buildHost = "localhost";
201             }
202         }
203         catch ( UnknownHostException ex )
204         {
205             fromName = "Continuum";
206         }
207 
208         // ----------------------------------------------------------------------
209         // From mailbox
210         // ----------------------------------------------------------------------
211 
212         if ( StringUtils.isEmpty( fromMailbox ) )
213         {
214             log.info( "The from mailbox is not configured, will use the nag email address from the project." );
215 
216             fromMailbox = null;
217         }
218         else
219         {
220             log.info( "Using '" + fromMailbox + "' as the from mailbox for all emails." );
221         }
222 
223         if ( StringUtils.isEmpty( fromName ) )
224         {
225             fromName = "Continuum@" + buildHost;
226         }
227 
228         log.info( "From name: " + fromName );
229 
230         log.info( "Build host name: " + buildHost );
231 
232         // ----------------------------------------------------------------------
233         //
234         // ----------------------------------------------------------------------
235 
236         formatterTool = new FormatterTool( timestampFormat );
237     }
238 
239     // ----------------------------------------------------------------------
240     // Notifier Implementation
241     // ----------------------------------------------------------------------
242 
243     public String getType()
244     {
245         return "mail";
246     }
247 
248     public void sendMessage( String messageId, MessageContext context )
249         throws NotificationException
250     {
251         Project project = context.getProject();
252         List<ProjectNotifier> notifiers = context.getNotifiers();
253         BuildResult build = context.getBuildResult();
254         BuildDefinition buildDefinition = context.getBuildDefinition();
255         ProjectScmRoot projectScmRoot = context.getProjectScmRoot();
256 
257         boolean isPrepareBuildComplete =
258             messageId.equals( ContinuumNotificationDispatcher.MESSAGE_ID_PREPARE_BUILD_COMPLETE );
259 
260         if ( projectScmRoot == null && isPrepareBuildComplete )
261         {
262             return;
263         }
264 
265         // ----------------------------------------------------------------------
266         // If there wasn't any building done, don't notify
267         // ----------------------------------------------------------------------
268 
269         if ( build == null && !isPrepareBuildComplete )
270         {
271             return;
272         }
273 
274         // ----------------------------------------------------------------------
275         // Generate and send email
276         // ----------------------------------------------------------------------
277 
278         if ( messageId.equals( ContinuumNotificationDispatcher.MESSAGE_ID_BUILD_COMPLETE ) )
279         {
280             buildComplete( project, notifiers, build, messageId, context, buildDefinition );
281         }
282         else if ( isPrepareBuildComplete )
283         {
284             prepareBuildComplete( projectScmRoot, notifiers, messageId, context );
285         }
286     }
287 
288     private void buildComplete( Project project, List<ProjectNotifier> notifiers, BuildResult build, String messageId,
289                                 MessageContext context, BuildDefinition buildDefinition )
290         throws NotificationException
291     {
292         BuildResult previousBuild = getPreviousBuild( project, buildDefinition, build );
293 
294         List<ProjectNotifier> notifiersList = new ArrayList<ProjectNotifier>();
295         for ( ProjectNotifier notifier : notifiers )
296         {
297             // ----------------------------------------------------------------------
298             // Check if the mail should be sent at all
299             // ----------------------------------------------------------------------
300 
301             if ( shouldNotify( build, previousBuild, notifier ) )
302             {
303                 notifiersList.add( notifier );
304             }
305         }
306         buildComplete( project, notifiersList, build, previousBuild, messageId, context, buildDefinition );
307     }
308 
309     private void buildComplete( Project project, List<ProjectNotifier> notifiers, BuildResult build,
310                                 BuildResult previousBuild, String messageId, MessageContext messageContext,
311                                 BuildDefinition buildDefinition )
312         throws NotificationException
313     {
314         // ----------------------------------------------------------------------
315         // Generate the mail contents
316         // ----------------------------------------------------------------------
317 
318         String packageName = getClass().getPackage().getName().replace( '.', '/' );
319 
320         String templateName = packageName + "/templates/" + project.getExecutorId() + "/" + messageId + ".vm";
321 
322         StringWriter writer = new StringWriter();
323 
324         String content;
325 
326         try
327         {
328             VelocityContext context = new VelocityContext();
329 
330             context.put( "includeTestSummary", includeTestSummary );
331 
332             context.put( "includeOutput", includeBuildOutput );
333 
334             if ( includeBuildOutput )
335             {
336                 context.put( "buildOutput", getBuildOutput( project, build ) );
337             }
338 
339             if ( includeBuildSummary )
340             {
341                 context.put( "build", build );
342 
343                 ReportTestResult reportTestResult =
344                     reportTestSuiteGenerator.generateReportTestResult( build.getId(), project.getId() );
345 
346                 context.put( "testResult", reportTestResult );
347 
348                 context.put( "project", project );
349 
350                 context.put( "changesSinceLastSuccess",
351                              continuum.getChangesSinceLastSuccess( project.getId(), build.getId() ) );
352 
353                 context.put( "previousBuild", previousBuild );
354 
355                 // ----------------------------------------------------------------------
356                 // Tools
357                 // ----------------------------------------------------------------------
358 
359                 context.put( "formatter", formatterTool );
360 
361                 // TODO: Make the build host a part of the build
362 
363                 context.put( "buildHost", buildHost );
364 
365                 String osName = System.getProperty( "os.name" );
366 
367                 String osPatchLevel = System.getProperty( "sun.os.patch.level" );
368 
369                 if ( osPatchLevel != null )
370                 {
371                     osName = osName + "(" + osPatchLevel + ")";
372                 }
373 
374                 context.put( "osName", osName );
375 
376                 context.put( "javaVersion",
377                              System.getProperty( "java.version" ) + "(" + System.getProperty( "java.vendor" ) + ")" );
378 
379                 // TODO only in case of a java project ?
380                 context.put( "javaHomeInformations", getJavaHomeInformations( buildDefinition ) );
381 
382                 context.put( "builderVersions", getBuilderVersion( buildDefinition, project ) );
383             }
384 
385             // ----------------------------------------------------------------------
386             // Data objects
387             // ----------------------------------------------------------------------
388 
389             context.put( "reportUrl", getReportUrl( project, build, configurationService ) );
390 
391             // TODO put other profile env var could be a security if they provide passwords ?
392 
393             // ----------------------------------------------------------------------
394             // Generate
395             // ----------------------------------------------------------------------
396 
397             velocity.getEngine().mergeTemplate( templateName, context, writer );
398 
399             content = writer.getBuffer().toString();
400         }
401         catch ( ResourceNotFoundException e )
402         {
403             log.info( "No such template: '" + templateName + "'." );
404 
405             return;
406         }
407         catch ( Exception e )
408         {
409             throw new NotificationException( "Error while generating mail contents.", e );
410         }
411 
412         // ----------------------------------------------------------------------
413         // Send the mail
414         // ----------------------------------------------------------------------
415 
416         String subject;
417         try
418         {
419             subject = generateSubject( project, build );
420         }
421         catch ( Exception e )
422         {
423             throw new NotificationException( "Error while generating mail subject.", e );
424         }
425 
426         sendMessage( project, notifiers, subject, content, messageContext );
427     }
428 
429     private void prepareBuildComplete( ProjectScmRoot projectScmRoot, List<ProjectNotifier> notifiers, String messageId,
430                                        MessageContext messageContext )
431         throws NotificationException
432     {
433         // ----------------------------------------------------------------------
434         // Generate the mail contents
435         // ----------------------------------------------------------------------
436 
437         String packageName = getClass().getPackage().getName().replace( '.', '/' );
438 
439         String templateName = packageName + "/templates/" + messageId + ".vm";
440 
441         StringWriter writer = new StringWriter();
442 
443         String content;
444 
445         try
446         {
447             VelocityContext context = new VelocityContext();
448 
449             // ----------------------------------------------------------------------
450             // Data objects
451             // ----------------------------------------------------------------------
452 
453             context.put( "reportUrl",
454                          getReportUrl( projectScmRoot.getProjectGroup(), projectScmRoot, configurationService ) );
455 
456             context.put( "projectScmRoot", projectScmRoot );
457 
458             // TODO put other profile env var could be a security if they provide passwords ?
459 
460             // ----------------------------------------------------------------------
461             // Generate
462             // ----------------------------------------------------------------------
463 
464             velocity.getEngine().mergeTemplate( templateName, context, writer );
465 
466             content = writer.getBuffer().toString();
467         }
468         catch ( ResourceNotFoundException e )
469         {
470             log.info( "No such template: '" + templateName + "'." );
471 
472             return;
473         }
474         catch ( Exception e )
475         {
476             throw new NotificationException( "Error while generating mail contents.", e );
477         }
478 
479         // ----------------------------------------------------------------------
480         // Send the mail
481         // ----------------------------------------------------------------------
482 
483         String subject;
484         try
485         {
486             subject = generateSubject( projectScmRoot );
487         }
488         catch ( Exception e )
489         {
490             throw new NotificationException( "Error while generating mail subject.", e );
491         }
492 
493         sendMessage( projectScmRoot, notifiers, subject, content, messageContext );
494     }
495 
496     // ----------------------------------------------------------------------
497     //
498     // ----------------------------------------------------------------------
499 
500     private List<String> getJavaHomeInformations( BuildDefinition buildDefinition )
501         throws InstallationException
502     {
503         if ( buildDefinition == null )
504         {
505             return continuum.getInstallationService().getDefaultJdkInformations();
506         }
507         Profile profile = buildDefinition.getProfile();
508         if ( profile == null )
509         {
510             return continuum.getInstallationService().getDefaultJdkInformations();
511         }
512         return continuum.getInstallationService().getJdkInformations( profile.getJdk() );
513     }
514 
515     private List<String> getBuilderVersion( BuildDefinition buildDefinition, Project project )
516         throws InstallationException
517     {
518         ExecutorConfigurator executorConfigurator;
519         Installation builder = null;
520         Profile profile = null;
521         if ( buildDefinition != null )
522         {
523             profile = buildDefinition.getProfile();
524             if ( profile != null )
525             {
526                 builder = profile.getBuilder();
527             }
528         }
529         if ( builder != null )
530         {
531             executorConfigurator = continuum.getInstallationService().getExecutorConfigurator( builder.getType() );
532         }
533         else
534         {
535             // depends on ExecutorId
536             if ( MavenTwoBuildExecutor.ID.equals( project.getExecutorId() ) )
537             {
538                 executorConfigurator =
539                     continuum.getInstallationService().getExecutorConfigurator( InstallationService.MAVEN2_TYPE );
540             }
541             else if ( MavenOneBuildExecutor.ID.equals( project.getExecutorId() ) )
542             {
543                 executorConfigurator =
544                     continuum.getInstallationService().getExecutorConfigurator( InstallationService.MAVEN1_TYPE );
545             }
546             else if ( AntBuildExecutor.ID.equals( project.getExecutorId() ) )
547             {
548                 executorConfigurator =
549                     continuum.getInstallationService().getExecutorConfigurator( InstallationService.ANT_TYPE );
550             }
551             else
552             {
553                 return Arrays.asList( "No builder defined" );
554             }
555         }
556 
557         return continuum.getInstallationService().getExecutorConfiguratorVersion(
558             builder == null ? null : builder.getVarValue(), executorConfigurator, profile );
559     }
560 
561     private String generateSubject( Project project, BuildResult build )
562         throws Exception
563     {
564         String state = getState( project, build );
565 
566         VelocityContext context = new VelocityContext();
567         context.put( "project", project );
568         context.put( "build", build );
569         context.put( "state", state );
570 
571         StringWriter writer = new StringWriter();
572 
573         boolean velocityRes = velocity.getEngine().evaluate( context, writer, "subjectPattern", buildSubjectFormat );
574 
575         return writer.toString();
576     }
577 
578     private String generateSubject( ProjectScmRoot projectScmRoot )
579         throws Exception
580     {
581         String state = getState( projectScmRoot );
582 
583         VelocityContext context = new VelocityContext();
584         context.put( "projectScmRoot", projectScmRoot );
585         context.put( "state", state );
586 
587         StringWriter writer = new StringWriter();
588 
589         boolean velocityResults =
590             velocity.getEngine().evaluate( context, writer, "subjectPattern", prepareBuildSubjectFormat );
591 
592         return writer.toString();
593     }
594 
595     private String getState( Project project, BuildResult build )
596     {
597         int state = project.getState();
598 
599         if ( build != null )
600         {
601             state = build.getState();
602         }
603 
604         if ( state == ContinuumProjectState.OK )
605         {
606             return "SUCCESSFUL";
607         }
608         else if ( state == ContinuumProjectState.FAILED )
609         {
610             return "FAILURE";
611         }
612         else if ( state == ContinuumProjectState.ERROR )
613         {
614             return "ERROR";
615         }
616         else
617         {
618             log.warn( "Unknown build state " + state + " for project " + project.getId() );
619 
620             return "ERROR: Unknown build state " + state;
621         }
622     }
623 
624     private String getState( ProjectScmRoot projectScmRoot )
625     {
626         int state = projectScmRoot.getState();
627 
628         if ( state == ContinuumProjectState.UPDATED )
629         {
630             return "SUCCESSFUL";
631         }
632         else if ( state == ContinuumProjectState.ERROR )
633         {
634             return "ERROR";
635         }
636         else
637         {
638             log.warn(
639                 "Unknown prepare build state " + state + " for SCM Root URL " + projectScmRoot.getScmRootAddress() +
640                     " in projectGroup " + projectScmRoot.getProjectGroup().getId() );
641 
642             return "ERROR: Unknown build state " + state;
643         }
644     }
645 
646     private void sendMessage( Project project, List<ProjectNotifier> notifiers, String subject, String content,
647                               MessageContext context )
648         throws NotificationException
649     {
650         if ( notifiers.size() == 0 )
651         {
652             // This is a useful message for the users when debugging why they don't
653             // receive any mails
654 
655             log.info( "No mail notifier for '" + project.getName() + "'." );
656 
657             return;
658         }
659 
660         String fromMailbox = getFromMailbox( notifiers );
661 
662         if ( fromMailbox == null )
663         {
664             log.warn( project.getName() +
665                 ": Project is missing nag email and global from mailbox is missing, not sending mail." );
666 
667             return;
668         }
669 
670         try
671         {
672 
673             MimeMessage message = javaMailSender.createMimeMessage();
674 
675             message.addHeader( "X-Continuum-Build-Host", buildHost );
676 
677             message.addHeader( "X-Continuum-Project-Id", Integer.toString( project.getId() ) );
678 
679             message.addHeader( "X-Continuum-Project-Name", project.getName() );
680 
681             message.setSubject( subject );
682 
683             log.info( "Message Subject: '" + subject + "'." );
684 
685             message.setText( content );
686 
687             InternetAddress from = new InternetAddress( fromMailbox, fromName );
688 
689             message.setFrom( from );
690 
691             log.info( "Sending message: From '" + from + "'." );
692 
693             if ( StringUtils.isEmpty( toOverride ) )
694             {
695                 Set<String> listRecipents = new HashSet<String>();
696                 for ( ProjectNotifier notifier : notifiers )
697                 {
698                     Map<String, String> conf = notifier.getConfiguration();
699                     if ( conf != null )
700                     {
701                         String addressField = conf.get( ADDRESS_FIELD );
702 
703                         if ( StringUtils.isNotEmpty( addressField ) )
704                         {
705                             String[] addresses = StringUtils.split( addressField, "," );
706                             for ( String address : addresses )
707                             {
708                                 if (!listRecipents.contains(address.trim())) {
709                                     // [CONTINUUM-2281] Dont repeat addesss in recipents.
710                                     // TODO: set a proper name
711                                     InternetAddress to = new InternetAddress(address.trim());
712 
713                                     log.info("Recipient: To '" + to + "'.");
714                                     message.addRecipient(Message.RecipientType.TO, to);
715                                     listRecipents.add(address.trim());
716                                 }
717                             }
718 
719                         }
720 
721                         String committerField = (String) notifier.getConfiguration().get( COMMITTER_FIELD );
722                         if ( StringUtils.isNotEmpty( committerField ) && context.getBuildResult() != null )
723                         {
724                             if ( Boolean.parseBoolean( committerField ) )
725                             {
726                                 ScmResult scmResult = context.getBuildResult().getScmResult();
727                                 if ( scmResult != null && scmResult.getChanges() != null &&
728                                     !scmResult.getChanges().isEmpty() )
729                                 {
730                                     List<ProjectDeveloper> developers = project.getDevelopers();
731                                     if ( developers == null || developers.isEmpty() )
732                                     {
733                                         log.warn( "No developers have been configured...notifcation email " +
734                                             "will not be sent" );
735                                         return;
736                                     }
737 
738                                     Map<String, String> developerToEmailMap = mapDevelopersToRecipients( developers );
739 
740                                     List<ChangeSet> changes = scmResult.getChanges();
741 
742                                     for ( ChangeSet changeSet : changes )
743                                     {
744                                         String scmId = changeSet.getAuthor();
745                                         if (StringUtils.isNotEmpty(scmId))
746                                         {
747                                             String email = developerToEmailMap.get( scmId );
748                                             if ( StringUtils.isEmpty( email ) )
749                                             {
750                                                 //TODO: Add a default domain so mail address won't be required
751                                                 log.warn(
752                                                     "no email address is defined in developers list for '" + scmId +
753                                                         "' scm id." );
754                                             }
755                                             else if (!listRecipents.contains(email.trim()))
756                                             {  
757                                                 // [CONTINUUM-2281] Dont repeat addesss in recipents.)
758                                                 // TODO: set a proper name
759                                                 InternetAddress to = new InternetAddress( email.trim() );
760                                                 log.info( "Recipient: To '" + to + "'." );
761 
762                                                 message.addRecipient( Message.RecipientType.TO, to );
763                                                 listRecipents.add(email.trim());
764                                             }
765                                         }
766                                     }
767                                 }
768                             }
769                         }
770                     }
771                 }
772             }
773             else
774             {
775                 // TODO: use configuration file instead of to load it fron component configuration
776                 // TODO: set a proper name
777                 InternetAddress to = new InternetAddress( toOverride.trim() );
778                 log.info( "Recipient: To '" + to + "'." );
779 
780                 message.addRecipient( Message.RecipientType.TO, to );
781             }
782 
783             message.setSentDate( new Date() );
784 
785             if ( message.getAllRecipients() != null && ( message.getAllRecipients() ).length > 0 )
786             {
787                 javaMailSender.send( message );
788             }
789         }
790         catch ( AddressException ex )
791         {
792             throw new NotificationException( "Exception while sending message.", ex );
793         }
794         catch ( MessagingException ex )
795         {
796             throw new NotificationException( "Exception while sending message.", ex );
797         }
798         catch ( UnsupportedEncodingException ex )
799         {
800             throw new NotificationException( "Exception while sending message.", ex );
801         }
802     }
803 
804     private void sendMessage( ProjectScmRoot projectScmRoot, List<ProjectNotifier> notifiers, String subject,
805                               String content, MessageContext context )
806         throws NotificationException
807     {
808         ProjectGroup projectGroup = projectScmRoot.getProjectGroup();
809 
810         if ( notifiers.size() == 0 )
811         {
812             // This is a useful message for the users when debugging why they don't
813             // receive any mails
814 
815             log.info( "No mail notifier for '" + projectGroup.getName() + "'." );
816 
817             return;
818         }
819 
820         String fromMailbox = getFromMailbox( notifiers );
821 
822         if ( fromMailbox == null )
823         {
824             log.warn( projectGroup.getName() +
825                 ": ProjectGroup is missing nag email and global from mailbox is missing, not sending mail." );
826 
827             return;
828         }
829 
830         MimeMessage message = javaMailSender.createMimeMessage();
831 
832         try
833         {
834             message.setSubject( subject );
835 
836             log.info( "Message Subject: '" + subject + "'." );
837 
838             message.setText( content );
839 
840             InternetAddress from = new InternetAddress( fromMailbox, fromName );
841 
842             message.setFrom( from );
843 
844             log.info( "Sending message: From '" + from + "'." );
845 
846             if ( StringUtils.isEmpty( toOverride ) )
847             {
848                 for ( ProjectNotifier notifier : notifiers )
849                 {
850                     if ( !shouldNotify( projectScmRoot, notifier ) )
851                     {
852                         continue;
853                     }
854 
855                     Map<String, String> conf = notifier.getConfiguration();
856                     if ( conf != null )
857                     {
858                         String addressField = conf.get( ADDRESS_FIELD );
859 
860                         if ( StringUtils.isNotEmpty( addressField ) )
861                         {
862                             String[] addresses = StringUtils.split( addressField, "," );
863 
864                             for ( String address : addresses )
865                             {
866                                 // TODO: set a proper name
867                                 InternetAddress to = new InternetAddress( address.trim() );
868 
869                                 log.info( "Recipient: To '" + to + "'." );
870                                 message.addRecipient( Message.RecipientType.TO, to );
871                             }
872                         }
873                     }
874                 }
875             }
876             else
877             {
878                 // TODO: use configuration file instead of to load it fron component configuration
879                 // TODO: set a proper name
880                 InternetAddress to = new InternetAddress( toOverride.trim() );
881                 log.info( "Recipient: To '" + to + "'." );
882 
883                 message.addRecipient( Message.RecipientType.TO, to );
884             }
885 
886             message.setSentDate( new Date() );
887 
888             if ( message.getAllRecipients() != null && ( message.getAllRecipients() ).length > 0 )
889             {
890                 javaMailSender.send( message );
891             }
892         }
893         catch ( AddressException ex )
894         {
895             throw new NotificationException( "Exception while sending message.", ex );
896         }
897         catch ( MessagingException ex )
898         {
899             throw new NotificationException( "Exception while sending message.", ex );
900         }
901         catch ( UnsupportedEncodingException ex )
902         {
903             throw new NotificationException( "Exception while sending message.", ex );
904         }
905     }
906 
907     private Map<String, String> mapDevelopersToRecipients( List<ProjectDeveloper> developers )
908     {
909         Map<String, String> developersMap = new HashMap<String, String>();
910 
911         for ( ProjectDeveloper developer : developers )
912         {
913             if ( StringUtils.isNotEmpty( developer.getScmId() ) && StringUtils.isNotEmpty( developer.getEmail() ) )
914             {
915                 developersMap.put( developer.getScmId(), developer.getEmail() );
916             }
917         }
918 
919         return developersMap;
920     }
921 
922     private String getFromMailbox( List<ProjectNotifier> notifiers )
923     {
924         if ( fromMailbox != null )
925         {
926             return fromMailbox;
927         }
928 
929         String address = null;
930 
931         for ( ProjectNotifier notifier : notifiers )
932         {
933             Map<String, String> configuration = notifier.getConfiguration();
934             if ( configuration != null && StringUtils.isNotEmpty( configuration.get( ADDRESS_FIELD ) ) )
935             {
936                 address = configuration.get( ADDRESS_FIELD );
937                 break;
938             }
939         }
940 
941         if ( StringUtils.isEmpty( address ) )
942         {
943             return FALLBACK_FROM_MAILBOX;
944         }
945         // olamy : CONTINUUM-860 if address contains commas we use only the first one
946         if ( address != null && address.contains( "," ) )
947         {
948             String[] addresses = StringUtils.split( address, "," );
949             return addresses[0];
950         }
951         return address;
952     }
953 
954     public String getBuildHost()
955     {
956         return buildHost;
957     }
958 
959     public void setBuildHost( String buildHost )
960     {
961         this.buildHost = buildHost;
962     }
963 
964     public String getToOverride()
965     {
966         return toOverride;
967     }
968 
969     public void setToOverride( String toOverride )
970     {
971         this.toOverride = toOverride;
972     }
973 }