View Javadoc

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 }