1 | package org.apache.maven.continuum.scm.queue; |
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.util.ArrayList; |
23 | import java.util.HashMap; |
24 | import java.util.List; |
25 | import java.util.Map; |
26 | import java.util.Set; |
27 | |
28 | import org.apache.continuum.dao.BuildDefinitionDao; |
29 | import org.apache.continuum.dao.BuildResultDao; |
30 | import org.apache.continuum.dao.ProjectDao; |
31 | import org.apache.continuum.dao.ProjectScmRootDao; |
32 | import org.apache.continuum.model.project.ProjectScmRoot; |
33 | import org.apache.continuum.taskqueue.PrepareBuildProjectsTask; |
34 | import org.apache.continuum.utils.ContinuumUtils; |
35 | import org.apache.continuum.utils.ProjectSorter; |
36 | import org.apache.maven.continuum.core.action.AbstractContinuumAction; |
37 | import org.apache.maven.continuum.core.action.CheckWorkingDirectoryAction; |
38 | import org.apache.maven.continuum.core.action.CheckoutProjectContinuumAction; |
39 | import org.apache.maven.continuum.core.action.UpdateWorkingDirectoryFromScmContinuumAction; |
40 | import org.apache.maven.continuum.model.project.BuildDefinition; |
41 | import org.apache.maven.continuum.model.project.BuildResult; |
42 | import org.apache.maven.continuum.model.project.Project; |
43 | import org.apache.maven.continuum.model.project.ProjectGroup; |
44 | import org.apache.maven.continuum.model.scm.ChangeSet; |
45 | import org.apache.maven.continuum.model.scm.ScmResult; |
46 | import org.apache.maven.continuum.notification.ContinuumNotificationDispatcher; |
47 | import org.apache.maven.continuum.project.ContinuumProjectState; |
48 | import org.apache.maven.continuum.store.ContinuumStoreException; |
49 | import org.apache.maven.continuum.utils.WorkingDirectoryService; |
50 | import org.codehaus.plexus.action.ActionManager; |
51 | import org.codehaus.plexus.action.ActionNotFoundException; |
52 | import org.codehaus.plexus.taskqueue.Task; |
53 | import org.codehaus.plexus.taskqueue.execution.TaskExecutionException; |
54 | import org.codehaus.plexus.taskqueue.execution.TaskExecutor; |
55 | import org.codehaus.plexus.util.StringUtils; |
56 | import org.slf4j.Logger; |
57 | import org.slf4j.LoggerFactory; |
58 | |
59 | /** |
60 | * @author <a href="mailto:ctan@apache.org">Maria Catherine Tan</a> |
61 | * @version $Id: PrepareBuildProjectsTaskExecutor.java 819456 2009-09-28 05:50:03Z ctan $ |
62 | * @plexus.component role="org.codehaus.plexus.taskqueue.execution.TaskExecutor" |
63 | * role-hint="prepare-build-project" |
64 | */ |
65 | public class PrepareBuildProjectsTaskExecutor |
66 | implements TaskExecutor |
67 | { |
68 | private static final Logger log = LoggerFactory.getLogger( PrepareBuildProjectsTaskExecutor.class ); |
69 | |
70 | /** |
71 | * @plexus.requirement |
72 | */ |
73 | private ActionManager actionManager; |
74 | |
75 | /** |
76 | * @plexus.requirement |
77 | */ |
78 | private ProjectDao projectDao; |
79 | |
80 | /** |
81 | * @plexus.requirement |
82 | */ |
83 | private BuildDefinitionDao buildDefinitionDao; |
84 | |
85 | /** |
86 | * @plexus.requirement |
87 | */ |
88 | private ProjectScmRootDao projectScmRootDao; |
89 | |
90 | /** |
91 | * @plexus.requirement |
92 | */ |
93 | private BuildResultDao buildResultDao; |
94 | |
95 | /** |
96 | * @plexus.requirement |
97 | */ |
98 | private WorkingDirectoryService workingDirectoryService; |
99 | |
100 | /** |
101 | * @plexus.requirement |
102 | */ |
103 | private ContinuumNotificationDispatcher notifierDispatcher; |
104 | |
105 | public void executeTask( Task task ) |
106 | throws TaskExecutionException |
107 | { |
108 | PrepareBuildProjectsTask prepareTask = (PrepareBuildProjectsTask) task; |
109 | |
110 | Map<Integer, Integer> projectsBuildDefinitionsMap = prepareTask.getProjectsBuildDefinitionsMap(); |
111 | int trigger = prepareTask.getTrigger(); |
112 | Set<Integer> projectsId = projectsBuildDefinitionsMap.keySet(); |
113 | Map<String, Object> context = new HashMap<String, Object>(); |
114 | Map<Integer, ScmResult> scmResultMap = new HashMap<Integer, ScmResult>(); |
115 | |
116 | try |
117 | { |
118 | for ( Integer projectId : projectsId ) |
119 | { |
120 | int buildDefinitionId = projectsBuildDefinitionsMap.get( projectId ); |
121 | |
122 | log.info( "Initializing prepare build" ); |
123 | context = initializeContext( projectId, buildDefinitionId ); |
124 | |
125 | log.info( |
126 | "Starting prepare build of project: " + AbstractContinuumAction.getProject( context ).getName() ); |
127 | startPrepareBuild( context ); |
128 | |
129 | if ( !checkProjectScmRoot( context ) ) |
130 | { |
131 | break; |
132 | } |
133 | |
134 | try |
135 | { |
136 | if ( AbstractContinuumAction.getBuildDefinition( context ).isBuildFresh() ) |
137 | { |
138 | log.info( "Purging existing working copy" ); |
139 | cleanWorkingDirectory( context ); |
140 | } |
141 | |
142 | // ---------------------------------------------------------------------- |
143 | // TODO: Centralize the error handling from the SCM related actions. |
144 | // ContinuumScmResult should return a ContinuumScmResult from all |
145 | // methods, even in a case of failure. |
146 | // ---------------------------------------------------------------------- |
147 | log.info( "Updating working dir" ); |
148 | updateWorkingDirectory( context ); |
149 | |
150 | log.info( "Merging SCM results" ); |
151 | //CONTINUUM-1393 |
152 | if ( !AbstractContinuumAction.getBuildDefinition( context ).isBuildFresh() ) |
153 | { |
154 | mergeScmResults( context ); |
155 | } |
156 | } |
157 | finally |
158 | { |
159 | log.info( |
160 | "Ending prepare build of project: " + AbstractContinuumAction.getProject( context ).getName() ); |
161 | scmResultMap.put( AbstractContinuumAction.getProjectId( context ), |
162 | AbstractContinuumAction.getScmResult( context, new ScmResult() ) ); |
163 | endProjectPrepareBuild( context ); |
164 | } |
165 | } |
166 | } |
167 | finally |
168 | { |
169 | log.info( "Ending prepare build" ); |
170 | endPrepareBuild( context ); |
171 | } |
172 | |
173 | if ( checkProjectScmRoot( context ) ) |
174 | { |
175 | int projectGroupId = AbstractContinuumAction.getProjectGroupId( context ); |
176 | buildProjects( projectGroupId, projectsBuildDefinitionsMap, trigger, scmResultMap ); |
177 | } |
178 | } |
179 | |
180 | private Map<String, Object> initializeContext( int projectId, int buildDefinitionId ) |
181 | throws TaskExecutionException |
182 | { |
183 | Map<String, Object> context = new HashMap<String, Object>(); |
184 | |
185 | try |
186 | { |
187 | Project project = projectDao.getProject( projectId ); |
188 | ProjectGroup projectGroup = project.getProjectGroup(); |
189 | |
190 | List<ProjectScmRoot> scmRoots = projectScmRootDao.getProjectScmRootByProjectGroup( projectGroup.getId() ); |
191 | String projectScmUrl = project.getScmUrl(); |
192 | |
193 | for ( ProjectScmRoot projectScmRoot : scmRoots ) |
194 | { |
195 | if ( projectScmUrl.startsWith( projectScmRoot.getScmRootAddress() ) ) |
196 | { |
197 | AbstractContinuumAction.setProjectScmRoot( context, projectScmRoot ); |
198 | break; |
199 | } |
200 | } |
201 | |
202 | AbstractContinuumAction.setProjectGroupId( context, projectGroup.getId() ); |
203 | AbstractContinuumAction.setProjectId( context, projectId ); |
204 | AbstractContinuumAction.setProject( context, project ); |
205 | |
206 | AbstractContinuumAction.setBuildDefinitionId( context, buildDefinitionId ); |
207 | AbstractContinuumAction.setBuildDefinition( context, |
208 | buildDefinitionDao.getBuildDefinition( buildDefinitionId ) ); |
209 | |
210 | BuildResult oldBuildResult = |
211 | buildResultDao.getLatestBuildResultForBuildDefinition( projectId, buildDefinitionId ); |
212 | |
213 | if ( oldBuildResult != null ) |
214 | { |
215 | AbstractContinuumAction.setOldScmResult( context, |
216 | getOldScmResults( projectId, oldBuildResult.getBuildNumber(), |
217 | oldBuildResult.getEndTime() ) ); |
218 | } |
219 | else |
220 | { |
221 | AbstractContinuumAction.setOldScmResult( context, null ); |
222 | } |
223 | } |
224 | catch ( ContinuumStoreException e ) |
225 | { |
226 | throw new TaskExecutionException( "Error initializing pre-build context", e ); |
227 | } |
228 | |
229 | return context; |
230 | } |
231 | |
232 | private void cleanWorkingDirectory( Map<String, Object> context ) |
233 | throws TaskExecutionException |
234 | { |
235 | performAction( "clean-working-directory", context ); |
236 | } |
237 | |
238 | private void updateWorkingDirectory( Map<String, Object> context ) |
239 | throws TaskExecutionException |
240 | { |
241 | performAction( "check-working-directory", context ); |
242 | |
243 | boolean workingDirectoryExists = CheckWorkingDirectoryAction.isWorkingDirectoryExist( context ); |
244 | |
245 | ScmResult scmResult; |
246 | |
247 | if ( workingDirectoryExists ) |
248 | { |
249 | performAction( "update-working-directory-from-scm", context ); |
250 | |
251 | scmResult = UpdateWorkingDirectoryFromScmContinuumAction.getUpdateScmResult( context ); |
252 | } |
253 | else |
254 | { |
255 | Project project = AbstractContinuumAction.getProject( context ); |
256 | |
257 | AbstractContinuumAction.setWorkingDirectory( context, workingDirectoryService.getWorkingDirectory( |
258 | project ).getAbsolutePath() ); |
259 | |
260 | performAction( "checkout-project", context ); |
261 | |
262 | scmResult = CheckoutProjectContinuumAction.getCheckoutResult( context, null ); |
263 | } |
264 | |
265 | // [CONTINUUM-2207] when returned scmResult is null, this causes a problem when building the project |
266 | if ( scmResult == null ) |
267 | { |
268 | log.debug( "Returned ScmResult is null when updating the working directory" ); |
269 | scmResult = new ScmResult(); |
270 | } |
271 | |
272 | AbstractContinuumAction.setScmResult( context, scmResult ); |
273 | } |
274 | |
275 | private boolean checkProjectScmRoot( Map<String, Object> context ) |
276 | throws TaskExecutionException |
277 | { |
278 | ProjectScmRoot projectScmRoot = AbstractContinuumAction.getProjectScmRoot( context ); |
279 | |
280 | // check state of scm root |
281 | return projectScmRoot.getState() != ContinuumProjectState.ERROR; |
282 | |
283 | } |
284 | |
285 | private void startPrepareBuild( Map<String, Object> context ) |
286 | throws TaskExecutionException |
287 | { |
288 | ProjectScmRoot projectScmRoot = AbstractContinuumAction.getProjectScmRoot( context ); |
289 | if ( projectScmRoot.getState() != ContinuumProjectState.UPDATING ) |
290 | { |
291 | try |
292 | { |
293 | projectScmRoot.setOldState( projectScmRoot.getState() ); |
294 | projectScmRoot.setState( ContinuumProjectState.UPDATING ); |
295 | projectScmRootDao.updateProjectScmRoot( projectScmRoot ); |
296 | } |
297 | catch ( ContinuumStoreException e ) |
298 | { |
299 | throw new TaskExecutionException( "Error persisting projectScmRoot", e ); |
300 | } |
301 | } |
302 | } |
303 | |
304 | private void endPrepareBuild( Map<String, Object> context ) |
305 | throws TaskExecutionException |
306 | { |
307 | ProjectScmRoot projectScmRoot = AbstractContinuumAction.getProjectScmRoot( context ); |
308 | |
309 | if ( projectScmRoot.getState() != ContinuumProjectState.ERROR ) |
310 | { |
311 | projectScmRoot.setState( ContinuumProjectState.UPDATED ); |
312 | projectScmRoot.setError( null ); |
313 | |
314 | try |
315 | { |
316 | projectScmRootDao.updateProjectScmRoot( projectScmRoot ); |
317 | } |
318 | catch ( ContinuumStoreException e ) |
319 | { |
320 | throw new TaskExecutionException( "Error persisting projectScmRoot", e ); |
321 | } |
322 | } |
323 | |
324 | notifierDispatcher.prepareBuildComplete( projectScmRoot ); |
325 | } |
326 | |
327 | /** |
328 | * @param context |
329 | * @throws TaskExecutionException |
330 | */ |
331 | private void endProjectPrepareBuild( Map<String, Object> context ) |
332 | throws TaskExecutionException |
333 | { |
334 | ScmResult scmResult = AbstractContinuumAction.getScmResult( context, null ); |
335 | |
336 | if ( scmResult == null || !scmResult.isSuccess() ) |
337 | { |
338 | String error = convertScmResultToError( scmResult ); |
339 | |
340 | updateProjectScmRoot( context, error ); |
341 | } |
342 | } |
343 | |
344 | private ScmResult getOldScmResults( int projectId, long startId, long fromDate ) |
345 | throws ContinuumStoreException |
346 | { |
347 | List<BuildResult> results = buildResultDao.getBuildResultsForProjectFromId( projectId, startId ); |
348 | |
349 | ScmResult res = new ScmResult(); |
350 | |
351 | if ( results != null && results.size() > 0 ) |
352 | { |
353 | for ( BuildResult result : results ) |
354 | { |
355 | ScmResult scmResult = result.getScmResult(); |
356 | |
357 | if ( scmResult != null ) |
358 | { |
359 | List<ChangeSet> changes = scmResult.getChanges(); |
360 | |
361 | if ( changes != null ) |
362 | { |
363 | for ( ChangeSet changeSet : changes ) |
364 | { |
365 | if ( changeSet.getDate() < fromDate ) |
366 | { |
367 | continue; |
368 | } |
369 | if ( !res.getChanges().contains( changeSet ) ) |
370 | { |
371 | res.addChange( changeSet ); |
372 | } |
373 | } |
374 | } |
375 | } |
376 | } |
377 | } |
378 | |
379 | return res; |
380 | } |
381 | |
382 | /** |
383 | * Merges scm results so we'll have all changes since last execution of current build definition |
384 | * |
385 | * @param context The build context |
386 | */ |
387 | private void mergeScmResults( Map<String, Object> context ) |
388 | { |
389 | ScmResult oldScmResult = AbstractContinuumAction.getOldScmResult( context ); |
390 | ScmResult newScmResult = AbstractContinuumAction.getScmResult( context, null ); |
391 | |
392 | if ( oldScmResult != null ) |
393 | { |
394 | if ( newScmResult == null ) |
395 | { |
396 | AbstractContinuumAction.setScmResult( context, oldScmResult ); |
397 | } |
398 | else |
399 | { |
400 | List<ChangeSet> oldChanges = oldScmResult.getChanges(); |
401 | |
402 | List<ChangeSet> newChanges = newScmResult.getChanges(); |
403 | |
404 | for ( ChangeSet change : newChanges ) |
405 | { |
406 | if ( !oldChanges.contains( change ) ) |
407 | { |
408 | oldChanges.add( change ); |
409 | } |
410 | } |
411 | |
412 | newScmResult.setChanges( oldChanges ); |
413 | } |
414 | } |
415 | } |
416 | |
417 | private void performAction( String actionName, Map<String, Object> context ) |
418 | throws TaskExecutionException |
419 | { |
420 | TaskExecutionException exception; |
421 | |
422 | try |
423 | { |
424 | log.info( "Performing action " + actionName ); |
425 | actionManager.lookup( actionName ).execute( context ); |
426 | return; |
427 | } |
428 | catch ( ActionNotFoundException e ) |
429 | { |
430 | exception = new TaskExecutionException( "Error looking up action '" + actionName + "'", e ); |
431 | } |
432 | catch ( Exception e ) |
433 | { |
434 | exception = new TaskExecutionException( "Error executing action '" + actionName + "'", e ); |
435 | } |
436 | |
437 | ScmResult result = new ScmResult(); |
438 | |
439 | result.setSuccess( false ); |
440 | |
441 | result.setException( ContinuumUtils.throwableToString( exception ) ); |
442 | |
443 | AbstractContinuumAction.setScmResult( context, result ); |
444 | |
445 | throw exception; |
446 | } |
447 | |
448 | private String convertScmResultToError( ScmResult result ) |
449 | { |
450 | String error = ""; |
451 | |
452 | if ( result == null ) |
453 | { |
454 | error = "Scm result is null."; |
455 | } |
456 | else |
457 | { |
458 | if ( result.getCommandLine() != null ) |
459 | { |
460 | error = "Command line: " + StringUtils.clean( result.getCommandLine() ) + |
461 | System.getProperty( "line.separator" ); |
462 | } |
463 | |
464 | if ( result.getProviderMessage() != null ) |
465 | { |
466 | error = "Provider message: " + StringUtils.clean( result.getProviderMessage() ) + |
467 | System.getProperty( "line.separator" ); |
468 | } |
469 | |
470 | if ( result.getCommandOutput() != null ) |
471 | { |
472 | error += "Command output: " + System.getProperty( "line.separator" ); |
473 | error += "-------------------------------------------------------------------------------" + |
474 | System.getProperty( "line.separator" ); |
475 | error += StringUtils.clean( result.getCommandOutput() ) + System.getProperty( "line.separator" ); |
476 | error += "-------------------------------------------------------------------------------" + |
477 | System.getProperty( "line.separator" ); |
478 | } |
479 | |
480 | if ( result.getException() != null ) |
481 | { |
482 | error += "Exception:" + System.getProperty( "line.separator" ); |
483 | error += result.getException(); |
484 | } |
485 | } |
486 | |
487 | return error; |
488 | } |
489 | |
490 | private void updateProjectScmRoot( Map<String, Object> context, String error ) |
491 | throws TaskExecutionException |
492 | { |
493 | ProjectScmRoot projectScmRoot = AbstractContinuumAction.getProjectScmRoot( context ); |
494 | |
495 | try |
496 | { |
497 | projectScmRoot.setState( ContinuumProjectState.ERROR ); |
498 | projectScmRoot.setError( error ); |
499 | |
500 | projectScmRootDao.updateProjectScmRoot( projectScmRoot ); |
501 | |
502 | AbstractContinuumAction.setProjectScmRoot( context, projectScmRoot ); |
503 | } |
504 | catch ( ContinuumStoreException e ) |
505 | { |
506 | throw new TaskExecutionException( "Error storing project scm root", e ); |
507 | } |
508 | } |
509 | |
510 | private void buildProjects( int projectGroupId, Map<Integer, Integer> projectsAndBuildDefinitionsMap, int trigger, |
511 | Map<Integer, ScmResult> scmResultMap ) |
512 | throws TaskExecutionException |
513 | { |
514 | List<Project> projects = projectDao.getProjectsWithDependenciesByGroupId( projectGroupId ); |
515 | List<Project> projectList; |
516 | |
517 | projectList = ProjectSorter.getSortedProjects( projects, log ); |
518 | |
519 | List<Project> projectsToBeBuilt = new ArrayList<Project>(); |
520 | Map<Integer, BuildDefinition> projectsBuildDefinitionsMap = new HashMap<Integer, BuildDefinition>(); |
521 | |
522 | for ( Project project : projectList ) |
523 | { |
524 | int buildDefinitionId; |
525 | |
526 | if ( projectsAndBuildDefinitionsMap.get( project.getId() ) != null ) |
527 | { |
528 | buildDefinitionId = projectsAndBuildDefinitionsMap.get( project.getId() ); |
529 | |
530 | try |
531 | { |
532 | BuildDefinition buildDefinition = buildDefinitionDao.getBuildDefinition( buildDefinitionId ); |
533 | projectsBuildDefinitionsMap.put( project.getId(), buildDefinition ); |
534 | projectsToBeBuilt.add( project ); |
535 | } |
536 | catch ( ContinuumStoreException e ) |
537 | { |
538 | log.error( "Error while creating build object", e ); |
539 | throw new TaskExecutionException( "Error while creating build object", e ); |
540 | } |
541 | } |
542 | } |
543 | |
544 | try |
545 | { |
546 | Map<String, Object> context = new HashMap<String, Object>(); |
547 | AbstractContinuumAction.setListOfProjects( context, projectsToBeBuilt ); |
548 | AbstractContinuumAction.setProjectsBuildDefinitionsMap( context, projectsBuildDefinitionsMap ); |
549 | AbstractContinuumAction.setTrigger( context, trigger ); |
550 | AbstractContinuumAction.setScmResultMap( context, scmResultMap ); |
551 | AbstractContinuumAction.setProjectGroupId( context, projectGroupId ); |
552 | |
553 | log.info( "Performing action create-build-project-task" ); |
554 | actionManager.lookup( "create-build-project-task" ).execute( context ); |
555 | } |
556 | catch ( ActionNotFoundException e ) |
557 | { |
558 | log.error( "Error looking up action 'build-project'" ); |
559 | throw new TaskExecutionException( "Error looking up action 'build-project'", e ); |
560 | } |
561 | catch ( Exception e ) |
562 | { |
563 | log.error( e.getMessage(), e ); |
564 | throw new TaskExecutionException( "Error executing action 'build-project'", e ); |
565 | } |
566 | } |
567 | } |