1 package org.apache.maven.continuum.buildcontroller;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.continuum.dao.BuildDefinitionDao;
23 import org.apache.continuum.dao.BuildResultDao;
24 import org.apache.continuum.dao.ProjectDao;
25 import org.apache.continuum.dao.ProjectGroupDao;
26 import org.apache.continuum.dao.ProjectScmRootDao;
27 import org.apache.continuum.model.project.ProjectScmRoot;
28 import org.apache.continuum.utils.ContinuumUtils;
29 import org.apache.continuum.utils.build.BuildTrigger;
30 import org.apache.maven.continuum.core.action.AbstractContinuumAction;
31 import org.apache.maven.continuum.core.action.ExecuteBuilderContinuumAction;
32 import org.apache.maven.continuum.execution.ContinuumBuildExecutor;
33 import org.apache.maven.continuum.execution.ContinuumBuildExecutorConstants;
34 import org.apache.maven.continuum.execution.manager.BuildExecutorManager;
35 import org.apache.maven.continuum.model.project.BuildDefinition;
36 import org.apache.maven.continuum.model.project.BuildResult;
37 import org.apache.maven.continuum.model.project.Project;
38 import org.apache.maven.continuum.model.project.ProjectDependency;
39 import org.apache.maven.continuum.model.project.ProjectGroup;
40 import org.apache.maven.continuum.model.scm.ChangeFile;
41 import org.apache.maven.continuum.model.scm.ChangeSet;
42 import org.apache.maven.continuum.model.scm.ScmResult;
43 import org.apache.maven.continuum.notification.ContinuumNotificationDispatcher;
44 import org.apache.maven.continuum.project.ContinuumProjectState;
45 import org.apache.maven.continuum.store.ContinuumObjectNotFoundException;
46 import org.apache.maven.continuum.store.ContinuumStoreException;
47 import org.apache.maven.continuum.utils.WorkingDirectoryService;
48 import org.apache.maven.scm.ScmException;
49 import org.apache.maven.scm.repository.ScmRepositoryException;
50 import org.codehaus.plexus.action.ActionManager;
51 import org.codehaus.plexus.action.ActionNotFoundException;
52 import org.codehaus.plexus.taskqueue.execution.TaskExecutionException;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 import java.util.ArrayList;
57 import java.util.Iterator;
58 import java.util.List;
59 import java.util.Map;
60
61
62
63
64
65
66 public class DefaultBuildController
67 implements BuildController
68 {
69 private static final Logger log = LoggerFactory.getLogger( DefaultBuildController.class );
70
71
72
73
74 private BuildDefinitionDao buildDefinitionDao;
75
76
77
78
79 private BuildResultDao buildResultDao;
80
81
82
83
84 private ProjectDao projectDao;
85
86
87
88
89 private ProjectGroupDao projectGroupDao;
90
91
92
93
94 private ProjectScmRootDao projectScmRootDao;
95
96
97
98
99 private ContinuumNotificationDispatcher notifierDispatcher;
100
101
102
103
104 private ActionManager actionManager;
105
106
107
108
109 private WorkingDirectoryService workingDirectoryService;
110
111
112
113
114 private BuildExecutorManager buildExecutorManager;
115
116
117
118
119
120
121
122
123
124
125
126
127 public void build( int projectId, int buildDefinitionId, BuildTrigger buildTrigger, ScmResult scmResult )
128 throws TaskExecutionException
129 {
130 log.info( "Initializing build" );
131 BuildContext context = initializeBuildContext( projectId, buildDefinitionId, buildTrigger, scmResult );
132
133
134 if ( !checkScmResult( context ) )
135 {
136 log.info( "Error updating from SCM, not building" );
137 return;
138 }
139
140 log.info( "Starting build of " + context.getProject().getName() );
141 startBuild( context );
142
143 try
144 {
145 checkProjectDependencies( context );
146
147 if ( !shouldBuild( context ) )
148 {
149 return;
150 }
151
152 Map<String, Object> actionContext = context.getActionContext();
153
154 try
155 {
156 performAction( "update-project-from-working-directory", context );
157 }
158 catch ( TaskExecutionException e )
159 {
160 updateBuildResult( context, ContinuumUtils.throwableToString( e ) );
161
162
163 log.error( "Error executing action update-project-from-working-directory '", e );
164 }
165
166 performAction( "execute-builder", context );
167
168 performAction( "deploy-artifact", context );
169
170 context.setCancelled( ExecuteBuilderContinuumAction.isCancelled( actionContext ) );
171
172 String s = AbstractContinuumAction.getBuildId( actionContext, null );
173
174 if ( s != null && !context.isCancelled() )
175 {
176 try
177 {
178 context.setBuildResult( buildResultDao.getBuildResult( Integer.valueOf( s ) ) );
179 }
180 catch ( NumberFormatException e )
181 {
182 throw new TaskExecutionException( "Internal error: build id not an integer", e );
183 }
184 catch ( ContinuumObjectNotFoundException e )
185 {
186 throw new TaskExecutionException( "Internal error: Cannot find build result", e );
187 }
188 catch ( ContinuumStoreException e )
189 {
190 throw new TaskExecutionException( "Error loading build result", e );
191 }
192 }
193 }
194 finally
195 {
196 endBuild( context );
197 }
198 }
199
200
201
202
203
204
205
206 private void endBuild( BuildContext context )
207 throws TaskExecutionException
208 {
209 Project project = context.getProject();
210
211 try
212 {
213 if ( project.getState() != ContinuumProjectState.NEW &&
214 project.getState() != ContinuumProjectState.CHECKEDOUT &&
215 project.getState() != ContinuumProjectState.OK && project.getState() != ContinuumProjectState.FAILED &&
216 project.getState() != ContinuumProjectState.ERROR && !context.isCancelled() )
217 {
218 try
219 {
220 String s = AbstractContinuumAction.getBuildId( context.getActionContext(), null );
221
222 if ( s != null )
223 {
224 BuildResult buildResult = buildResultDao.getBuildResult( Integer.valueOf( s ) );
225 project.setState( buildResult.getState() );
226 projectDao.updateProject( project );
227 }
228 }
229 catch ( ContinuumStoreException e )
230 {
231 throw new TaskExecutionException( "Error storing the project", e );
232 }
233 }
234 }
235 finally
236 {
237 if ( !context.isCancelled() )
238 {
239 notifierDispatcher.buildComplete( project, context.getBuildDefinition(), context.getBuildResult() );
240 }
241 }
242 }
243
244 private void updateBuildResult( BuildContext context, String error )
245 throws TaskExecutionException
246 {
247 BuildResult build = context.getBuildResult();
248
249 if ( build == null )
250 {
251 build = makeAndStoreBuildResult( context, error );
252 }
253 else
254 {
255 updateBuildResult( build, context );
256
257 build.setError( error );
258
259 try
260 {
261 buildResultDao.updateBuildResult( build );
262
263 build = buildResultDao.getBuildResult( build.getId() );
264
265 context.setBuildResult( build );
266 }
267 catch ( ContinuumStoreException e )
268 {
269 throw new TaskExecutionException( "Error updating build result", e );
270 }
271 }
272
273 context.getProject().setState( build.getState() );
274
275 try
276 {
277 projectDao.updateProject( context.getProject() );
278 }
279 catch ( ContinuumStoreException e )
280 {
281 throw new TaskExecutionException( "Error updating project", e );
282 }
283 }
284
285 private void updateBuildResult( BuildResult build, BuildContext context )
286 {
287 if ( build.getScmResult() == null && context.getScmResult() != null )
288 {
289 build.setScmResult( context.getScmResult() );
290 }
291
292 if ( build.getModifiedDependencies() == null && context.getModifiedDependencies() != null )
293 {
294 build.setModifiedDependencies( context.getModifiedDependencies() );
295 }
296 }
297
298 private void startBuild( BuildContext context )
299 throws TaskExecutionException
300 {
301
302 Project project = context.getProject();
303
304 project.setOldState( project.getState() );
305
306 project.setState( ContinuumProjectState.BUILDING );
307
308 try
309 {
310 projectDao.updateProject( project );
311 }
312 catch ( ContinuumStoreException e )
313 {
314 throw new TaskExecutionException( "Error persisting project", e );
315 }
316
317 notifierDispatcher.buildStarted( project, context.getBuildDefinition() );
318
319 }
320
321
322
323
324
325
326
327
328
329
330
331 @SuppressWarnings( "unchecked" )
332 protected BuildContext initializeBuildContext( int projectId, int buildDefinitionId, BuildTrigger buildTrigger,
333 ScmResult scmResult )
334 throws TaskExecutionException
335 {
336 BuildContext context = new BuildContext();
337
338 context.setStartTime( System.currentTimeMillis() );
339
340 Map actionContext = context.getActionContext();
341
342 try
343 {
344 Project project = projectDao.getProject( projectId );
345
346 context.setProject( project );
347
348 BuildDefinition buildDefinition = buildDefinitionDao.getBuildDefinition( buildDefinitionId );
349
350 BuildTrigger newBuildTrigger = buildTrigger;
351
352 if ( newBuildTrigger.getTrigger() == ContinuumProjectState.TRIGGER_SCHEDULED )
353 {
354 newBuildTrigger.setTriggeredBy( buildDefinition.getSchedule().getName() );
355 }
356
357 context.setBuildTrigger( newBuildTrigger );
358
359 context.setBuildDefinition( buildDefinition );
360
361 BuildResult oldBuildResult = buildResultDao.getLatestBuildResultForBuildDefinition( projectId,
362 buildDefinitionId );
363
364 context.setOldBuildResult( oldBuildResult );
365
366 context.setScmResult( scmResult );
367
368
369 ProjectGroup projectGroup = project.getProjectGroup();
370 List<ProjectScmRoot> scmRoots = projectScmRootDao.getProjectScmRootByProjectGroup( projectGroup.getId() );
371 String projectScmUrl = project.getScmUrl();
372 String projectScmRootAddress = "";
373
374 for ( ProjectScmRoot projectScmRoot : scmRoots )
375 {
376 projectScmRootAddress = projectScmRoot.getScmRootAddress();
377 if ( projectScmUrl.startsWith( projectScmRoot.getScmRootAddress() ) )
378 {
379 AbstractContinuumAction.setProjectScmRootUrl( actionContext, projectScmRoot.getScmRootAddress() );
380 break;
381 }
382 }
383
384 if ( project.isCheckedOutInSingleDirectory() )
385 {
386 List<Project> projectsInGroup = projectGroupDao.getProjectGroupWithProjects(
387 projectGroup.getId() ).getProjects();
388 List<Project> projectsWithCommonScmRoot = new ArrayList<Project>();
389 for ( Project projectInGroup : projectsInGroup )
390 {
391 if ( projectInGroup.getScmUrl().startsWith( projectScmRootAddress ) )
392 {
393 projectsWithCommonScmRoot.add( projectInGroup );
394 }
395 }
396 AbstractContinuumAction.setListOfProjectsInGroupWithCommonScmRoot( actionContext,
397 projectsWithCommonScmRoot );
398 }
399
400
401
402
403
404
405
406
407
408 }
409 catch ( ContinuumStoreException e )
410 {
411 throw new TaskExecutionException( "Error initializing the build context", e );
412 }
413
414
415
416 AbstractContinuumAction.setProjectId( actionContext, projectId );
417
418 AbstractContinuumAction.setProject( actionContext, context.getProject() );
419
420 AbstractContinuumAction.setBuildDefinitionId( actionContext, buildDefinitionId );
421
422 AbstractContinuumAction.setBuildDefinition( actionContext, context.getBuildDefinition() );
423
424 AbstractContinuumAction.setBuildTrigger( actionContext, buildTrigger );
425
426 AbstractContinuumAction.setScmResult( actionContext, context.getScmResult() );
427
428 if ( context.getOldBuildResult() != null )
429 {
430 AbstractContinuumAction.setOldBuildId( actionContext, context.getOldBuildResult().getId() );
431 }
432
433 return context;
434 }
435
436 private void performAction( String actionName, BuildContext context )
437 throws TaskExecutionException
438 {
439 String error;
440 TaskExecutionException exception;
441
442 try
443 {
444 log.info( "Performing action " + actionName );
445 actionManager.lookup( actionName ).execute( context.getActionContext() );
446 return;
447 }
448 catch ( ActionNotFoundException e )
449 {
450 error = ContinuumUtils.throwableToString( e );
451 exception = new TaskExecutionException( "Error looking up action '" + actionName + "'", e );
452 }
453 catch ( ScmRepositoryException e )
454 {
455 error = getValidationMessages( e ) + "\n" + ContinuumUtils.throwableToString( e );
456
457 exception = new TaskExecutionException( "SCM error while executing '" + actionName + "'", e );
458 }
459 catch ( ScmException e )
460 {
461 error = ContinuumUtils.throwableToString( e );
462
463 exception = new TaskExecutionException( "SCM error while executing '" + actionName + "'", e );
464 }
465 catch ( Exception e )
466 {
467 exception = new TaskExecutionException( "Error executing action '" + actionName + "'", e );
468 error = ContinuumUtils.throwableToString( exception );
469 }
470
471
472
473
474
475
476
477 try
478 {
479 updateBuildResult( context, error );
480 }
481 catch ( TaskExecutionException e )
482 {
483 log.error( "Error updating build result after receiving the following exception: ", exception );
484 throw e;
485 }
486
487 throw exception;
488 }
489
490 protected boolean shouldBuild( BuildContext context )
491 throws TaskExecutionException
492 {
493 BuildDefinition buildDefinition = context.getBuildDefinition();
494 if ( buildDefinition.isAlwaysBuild() )
495 {
496 log.info( "AlwaysBuild configured, building" );
497 return true;
498 }
499 if ( context.getOldBuildResult() == null )
500 {
501 log.info( "The project has never been built with the current build definition, building" );
502 return true;
503 }
504
505 Project project = context.getProject();
506
507
508 if ( project.getOldState() == ContinuumProjectState.ERROR ||
509 context.getOldBuildResult().getState() == ContinuumProjectState.ERROR )
510 {
511 log.info( "Latest state was 'ERROR', building" );
512 return true;
513 }
514
515 if ( context.getBuildTrigger().getTrigger() == ContinuumProjectState.TRIGGER_FORCED )
516 {
517 log.info( "The project build is forced, building" );
518 return true;
519 }
520
521 boolean shouldBuild = false;
522
523 boolean allChangesUnknown = true;
524
525 if ( project.getOldState() != ContinuumProjectState.NEW &&
526 project.getOldState() != ContinuumProjectState.CHECKEDOUT &&
527 context.getBuildTrigger().getTrigger() != ContinuumProjectState.TRIGGER_FORCED &&
528 project.getState() != ContinuumProjectState.NEW && project.getState() != ContinuumProjectState.CHECKEDOUT )
529 {
530
531 if ( context.getScmResult() != null )
532 {
533 allChangesUnknown = checkAllChangesUnknown( context.getScmResult().getChanges() );
534 }
535
536 if ( allChangesUnknown )
537 {
538 if ( context.getScmResult() != null && !context.getScmResult().getChanges().isEmpty() )
539 {
540 log.info(
541 "The project was not built because all changes are unknown (maybe local modifications or ignored files not defined in your SCM tool." );
542 }
543 else
544 {
545 log.info(
546 "The project was not built because no changes were detected in sources since the last build." );
547 }
548 }
549
550
551 if ( context.getModifiedDependencies() != null && !context.getModifiedDependencies().isEmpty() )
552 {
553 log.info( "Found dependencies changes, building" );
554 shouldBuild = true;
555 }
556 }
557
558
559 if ( !shouldBuild && ( ( !allChangesUnknown && context.getScmResult() != null &&
560 !context.getScmResult().getChanges().isEmpty() ) || project.getExecutorId().equals(
561 ContinuumBuildExecutorConstants.MAVEN_TWO_BUILD_EXECUTOR ) ) )
562 {
563 try
564 {
565 ContinuumBuildExecutor executor = buildExecutorManager.getBuildExecutor( project.getExecutorId() );
566
567 Map<String, Object> actionContext = context.getActionContext();
568 List<Project> projectsWithCommonScmRoot =
569 AbstractContinuumAction.getListOfProjectsInGroupWithCommonScmRoot( actionContext );
570 String projectScmRootUrl = AbstractContinuumAction.getProjectScmRootUrl( actionContext,
571 project.getScmUrl() );
572
573 if ( executor == null )
574 {
575 log.warn( "No continuum build executor found for project " + project.getId() +
576 " with executor '" + project.getExecutorId() + "'" );
577 }
578 else if ( context.getScmResult() != null )
579 {
580 shouldBuild = executor.shouldBuild( context.getScmResult().getChanges(), project,
581 workingDirectoryService.getWorkingDirectory( project,
582 projectScmRootUrl,
583 projectsWithCommonScmRoot ),
584 context.getBuildDefinition() );
585 }
586 }
587 catch ( Exception e )
588 {
589 updateBuildResult( context, ContinuumUtils.throwableToString( e ) );
590 throw new TaskExecutionException( "Can't determine if the project should build or not", e );
591 }
592 }
593
594 if ( shouldBuild )
595 {
596 log.info( "Changes found in the current project, building" );
597 }
598 else
599 {
600 project.setState( project.getOldState() );
601
602 project.setOldState( 0 );
603
604 try
605 {
606 projectDao.updateProject( project );
607 }
608 catch ( ContinuumStoreException e )
609 {
610 throw new TaskExecutionException( "Error storing project", e );
611 }
612 log.info( "No changes in the current project, not building" );
613
614 }
615
616 return shouldBuild;
617 }
618
619 private boolean checkAllChangesUnknown( List<ChangeSet> changes )
620 {
621 for ( ChangeSet changeSet : changes )
622 {
623 List<ChangeFile> changeFiles = changeSet.getFiles();
624
625 for ( ChangeFile changeFile : changeFiles )
626 {
627 if ( !"unknown".equalsIgnoreCase( changeFile.getStatus() ) )
628 {
629 return false;
630 }
631 }
632 }
633
634 return true;
635 }
636
637 private String getValidationMessages( ScmRepositoryException ex )
638 {
639 List<String> messages = ex.getValidationMessages();
640
641 StringBuffer message = new StringBuffer();
642
643 if ( messages != null && !messages.isEmpty() )
644 {
645 for ( Iterator<String> i = messages.iterator(); i.hasNext(); )
646 {
647 message.append( i.next() );
648
649 if ( i.hasNext() )
650 {
651 message.append( System.getProperty( "line.separator" ) );
652 }
653 }
654 }
655 return message.toString();
656 }
657
658 protected void checkProjectDependencies( BuildContext context )
659 {
660 if ( context.getOldBuildResult() == null )
661 {
662 return;
663 }
664
665 try
666 {
667 Project project = projectDao.getProjectWithDependencies( context.getProject().getId() );
668 List<ProjectDependency> dependencies = project.getDependencies();
669
670 if ( dependencies == null )
671 {
672 dependencies = new ArrayList<ProjectDependency>();
673 }
674
675 if ( project.getParent() != null )
676 {
677 dependencies.add( project.getParent() );
678 }
679
680 if ( dependencies.isEmpty() )
681 {
682 return;
683 }
684
685 List<ProjectDependency> modifiedDependencies = new ArrayList<ProjectDependency>();
686
687 for ( ProjectDependency dep : dependencies )
688 {
689 Project dependencyProject = projectDao.getProject( dep.getGroupId(), dep.getArtifactId(),
690 dep.getVersion() );
691
692 if ( dependencyProject != null )
693 {
694 long nbBuild = buildResultDao.getNbBuildResultsInSuccessForProject( dependencyProject.getId(),
695 context.getOldBuildResult().getEndTime() );
696 if ( nbBuild > 0 )
697 {
698 log.debug( "Dependency changed: " + dep.getGroupId() + ":" + dep.getArtifactId() + ":" +
699 dep.getVersion() );
700 modifiedDependencies.add( dep );
701 }
702 else
703 {
704 log.debug( "Dependency not changed: " + dep.getGroupId() + ":" + dep.getArtifactId() + ":" +
705 dep.getVersion() );
706 }
707 }
708 else
709 {
710 log.debug( "Skip non Continuum project: " + dep.getGroupId() + ":" + dep.getArtifactId() + ":" +
711 dep.getVersion() );
712 }
713 }
714
715 context.setModifiedDependencies( modifiedDependencies );
716 AbstractContinuumAction.setUpdatedDependencies( context.getActionContext(), modifiedDependencies );
717 }
718 catch ( ContinuumStoreException e )
719 {
720 log.warn( "Can't get the project dependencies", e );
721 }
722 }
723
724
725
726
727
728 private BuildResult makeAndStoreBuildResult( BuildContext context, String error )
729 throws TaskExecutionException
730 {
731
732
733
734 BuildResult build = new BuildResult();
735
736 build.setState( ContinuumProjectState.ERROR );
737
738 build.setTrigger( context.getBuildTrigger().getTrigger() );
739
740 build.setUsername( context.getBuildTrigger().getTriggeredBy() );
741
742 build.setStartTime( context.getStartTime() );
743
744 build.setEndTime( System.currentTimeMillis() );
745
746 updateBuildResult( build, context );
747
748 build.setScmResult( context.getScmResult() );
749
750 build.setBuildDefinition( context.getBuildDefinition() );
751
752 if ( error != null )
753 {
754 build.setError( error );
755 }
756
757 try
758 {
759 buildResultDao.addBuildResult( context.getProject(), build );
760
761 build = buildResultDao.getBuildResult( build.getId() );
762
763 context.setBuildResult( build );
764
765 return build;
766 }
767 catch ( ContinuumStoreException e )
768 {
769 throw new TaskExecutionException( "Error storing build result", e );
770 }
771 }
772
773
774
775
776
777
778
779
780 private boolean checkScmResult( BuildContext context )
781 throws TaskExecutionException
782 {
783 Project project = context.getProject();
784
785 int projectGroupId = project.getProjectGroup().getId();
786
787 List<ProjectScmRoot> scmRoots = projectScmRootDao.getProjectScmRootByProjectGroup( projectGroupId );
788
789 for ( ProjectScmRoot projectScmRoot : scmRoots )
790 {
791 if ( project.getScmUrl().startsWith( projectScmRoot.getScmRootAddress() ) )
792 {
793 if ( projectScmRoot.getState() == ContinuumProjectState.UPDATED )
794 {
795 return true;
796 }
797
798 break;
799 }
800 }
801
802 return false;
803 }
804 }