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