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