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 | } |