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