View Javadoc
1   package org.apache.maven.shared.release;
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.io.File;
23  import java.util.Collections;
24  import java.util.LinkedHashSet;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import org.apache.commons.lang3.BooleanUtils;
30  import org.apache.maven.shared.release.config.ReleaseDescriptor;
31  import org.apache.maven.shared.release.config.ReleaseDescriptorBuilder;
32  import org.apache.maven.shared.release.config.ReleaseDescriptorBuilder.BuilderReleaseDescriptor;
33  import org.apache.maven.shared.release.config.ReleaseDescriptorStore;
34  import org.apache.maven.shared.release.config.ReleaseDescriptorStoreException;
35  import org.apache.maven.shared.release.config.ReleaseUtils;
36  import org.apache.maven.shared.release.phase.ReleasePhase;
37  import org.apache.maven.shared.release.phase.ResourceGenerator;
38  import org.apache.maven.shared.release.strategy.Strategy;
39  import org.codehaus.plexus.component.annotations.Component;
40  import org.codehaus.plexus.component.annotations.Requirement;
41  import org.codehaus.plexus.logging.AbstractLogEnabled;
42  import org.codehaus.plexus.util.StringUtils;
43  
44  /**
45   * Implementation of the release manager.
46   *
47   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
48   */
49  @Component( role = ReleaseManager.class )
50  public class DefaultReleaseManager
51      extends AbstractLogEnabled
52      implements ReleaseManager
53  {
54      @Requirement
55      private Map<String, Strategy> strategies;
56  
57      /**
58       * The available phases.
59       */
60      @Requirement
61      private Map<String, ReleasePhase> releasePhases;
62  
63      /**
64       * The configuration storage.
65       */
66      @Requirement( hint = "properties" )
67      private ReleaseDescriptorStore configStore;
68  
69      private static final int PHASE_SKIP = 0, PHASE_START = 1, PHASE_END = 2, GOAL_END = 12, ERROR = 99;
70  
71      @Override
72      public ReleaseResult prepareWithResult( ReleasePrepareRequest prepareRequest )
73      {
74          ReleaseResult result = new ReleaseResult();
75  
76          result.setStartTime( System.currentTimeMillis() );
77  
78          try
79          {
80              prepare( prepareRequest, result );
81  
82              result.setResultCode( ReleaseResult.SUCCESS );
83          }
84          catch ( ReleaseExecutionException | ReleaseFailureException e )
85          {
86              captureException( result, prepareRequest.getReleaseManagerListener(), e );
87          }
88          finally
89          {
90              result.setEndTime( System.currentTimeMillis() );
91          }
92  
93          return result;
94      }
95  
96      @Override
97      public void prepare( ReleasePrepareRequest prepareRequest )
98          throws ReleaseExecutionException, ReleaseFailureException
99      {
100         prepare( prepareRequest, new ReleaseResult() );
101     }
102 
103     private void prepare( ReleasePrepareRequest prepareRequest, ReleaseResult result )
104         throws ReleaseExecutionException, ReleaseFailureException
105     {
106 
107         final ReleaseDescriptorBuilder builder = prepareRequest.getReleaseDescriptorBuilder();
108 
109         // Create a config containing values from the session properties (ie command line properties with cli).
110         ReleaseUtils.copyPropertiesToReleaseDescriptor( prepareRequest.getUserProperties(),
111                 new ReleaseDescriptorBuilder()
112                 {
113                     public ReleaseDescriptorBuilder addDevelopmentVersion( String key,
114                                                                            String value )
115                     {
116                         builder.addDevelopmentVersion( key, value );
117                         return this;
118                     }
119 
120                     public ReleaseDescriptorBuilder addReleaseVersion( String key,
121                                                                        String value )
122                     {
123                         builder.addReleaseVersion( key, value );
124                         return this;
125                     }
126 
127                     public ReleaseDescriptorBuilder addDependencyReleaseVersion( String dependencyKey,
128                                                                                 String version )
129                     {
130                         builder.addDependencyReleaseVersion( dependencyKey, version );
131                         return this;
132                     }
133 
134                     public ReleaseDescriptorBuilder addDependencyDevelopmentVersion( String dependencyKey,
135                                                                                     String version )
136                     {
137                         builder.addDependencyDevelopmentVersion( dependencyKey, version );
138                         return this;
139                     }
140                 } );
141 
142         BuilderReleaseDescriptor config;
143         if ( BooleanUtils.isNotFalse( prepareRequest.getResume() ) )
144         {
145             config = loadReleaseDescriptor( builder, prepareRequest.getReleaseManagerListener() );
146         }
147         else
148         {
149             config = ReleaseUtils.buildReleaseDescriptor( builder );
150         }
151 
152         Strategy releaseStrategy = getStrategy( config.getReleaseStrategyId() );
153 
154         List<String> preparePhases = getGoalPhases( releaseStrategy, "prepare" );
155 
156         goalStart( prepareRequest.getReleaseManagerListener(), "prepare", preparePhases );
157 
158         // Later, it would be a good idea to introduce a proper workflow tool so that the release can be made up of a
159         // more flexible set of steps.
160 
161         String completedPhase = config.getCompletedPhase();
162         int index = preparePhases.indexOf( completedPhase );
163 
164         for ( int idx = 0; idx <= index; idx++ )
165         {
166             updateListener( prepareRequest.getReleaseManagerListener(), preparePhases.get( idx ), PHASE_SKIP );
167         }
168 
169         if ( index == preparePhases.size() - 1 )
170         {
171             logInfo( result, "Release preparation already completed. You can now continue with release:perform, "
172                 + "or start again using the -Dresume=false flag" );
173         }
174         else if ( index >= 0 )
175         {
176             logInfo( result, "Resuming release from phase '" + preparePhases.get( index + 1 ) + "'" );
177         }
178 
179         // start from next phase
180         for ( int i = index + 1; i < preparePhases.size(); i++ )
181         {
182             String name = preparePhases.get( i );
183 
184             ReleasePhase phase = releasePhases.get( name );
185 
186             if ( phase == null )
187             {
188                 throw new ReleaseExecutionException( "Unable to find phase '" + name + "' to execute" );
189             }
190 
191             updateListener( prepareRequest.getReleaseManagerListener(), name, PHASE_START );
192 
193             ReleaseResult phaseResult = null;
194             try
195             {
196                 if ( BooleanUtils.isTrue( prepareRequest.getDryRun() ) )
197                 {
198                     phaseResult = phase.simulate( config,
199                                                   prepareRequest.getReleaseEnvironment(),
200                                                   prepareRequest.getReactorProjects() );
201                 }
202                 else
203                 {
204                     phaseResult = phase.execute( config,
205                                                  prepareRequest.getReleaseEnvironment(),
206                                                  prepareRequest.getReactorProjects() );
207                 }
208             }
209             finally
210             {
211                 if ( result != null && phaseResult != null )
212                 {
213                     result.appendOutput(  phaseResult.getOutput() );
214                 }
215             }
216 
217             config.setCompletedPhase( name );
218             try
219             {
220                 configStore.write( config );
221             }
222             catch ( ReleaseDescriptorStoreException e )
223             {
224                 // TODO: rollback?
225                 throw new ReleaseExecutionException( "Error writing release properties after completing phase", e );
226             }
227 
228             updateListener( prepareRequest.getReleaseManagerListener(), name, PHASE_END );
229         }
230 
231         updateListener( prepareRequest.getReleaseManagerListener(), "prepare", GOAL_END );
232     }
233 
234     @Override
235     public void rollback( ReleaseRollbackRequest rollbackRequest )
236         throws ReleaseExecutionException, ReleaseFailureException
237     {
238         ReleaseDescriptor releaseDescriptor =
239             loadReleaseDescriptor( rollbackRequest.getReleaseDescriptorBuilder(), null );
240 
241         Strategy releaseStrategy = getStrategy( releaseDescriptor.getReleaseStrategyId() );
242 
243         List<String> rollbackPhases = getGoalPhases( releaseStrategy, "rollback" );
244 
245         goalStart( rollbackRequest.getReleaseManagerListener(), "rollback", rollbackPhases );
246 
247         for ( String name : rollbackPhases )
248         {
249             ReleasePhase phase = releasePhases.get( name );
250 
251             if ( phase == null )
252             {
253                 throw new ReleaseExecutionException( "Unable to find phase '" + name + "' to execute" );
254             }
255 
256             updateListener( rollbackRequest.getReleaseManagerListener(), name, PHASE_START );
257             phase.execute( releaseDescriptor,
258                            rollbackRequest.getReleaseEnvironment(),
259                            rollbackRequest.getReactorProjects() );
260             updateListener( rollbackRequest.getReleaseManagerListener(), name, PHASE_END );
261         }
262 
263         //call release:clean so that resume will not be possible anymore after a rollback
264         clean( rollbackRequest );
265         updateListener( rollbackRequest.getReleaseManagerListener(), "rollback", GOAL_END );
266     }
267 
268     @Override
269     public ReleaseResult performWithResult( ReleasePerformRequest performRequest )
270     {
271         ReleaseResult result = new ReleaseResult();
272 
273         try
274         {
275             result.setStartTime( System.currentTimeMillis() );
276 
277             perform( performRequest, result );
278 
279             result.setResultCode( ReleaseResult.SUCCESS );
280         }
281         catch ( ReleaseExecutionException | ReleaseFailureException e )
282         {
283             captureException( result, performRequest.getReleaseManagerListener(), e );
284         }
285         finally
286         {
287             result.setEndTime( System.currentTimeMillis() );
288         }
289 
290         return result;
291     }
292 
293     @Override
294     public void perform( ReleasePerformRequest performRequest )
295         throws ReleaseExecutionException, ReleaseFailureException
296     {
297         perform( performRequest, new ReleaseResult() );
298     }
299 
300     private void perform( ReleasePerformRequest performRequest, ReleaseResult result )
301         throws ReleaseExecutionException, ReleaseFailureException
302     {
303         ReleaseDescriptor releaseDescriptor =
304             loadReleaseDescriptor( performRequest.getReleaseDescriptorBuilder(),
305                                    performRequest.getReleaseManagerListener() );
306 
307         Strategy releaseStrategy = getStrategy( releaseDescriptor.getReleaseStrategyId() );
308 
309         List<String> performPhases = getGoalPhases( releaseStrategy, "perform" );
310 
311         goalStart( performRequest.getReleaseManagerListener(), "perform", performPhases );
312 
313         for ( String name : performPhases )
314         {
315             ReleasePhase phase = releasePhases.get( name );
316 
317             if ( phase == null )
318             {
319                 throw new ReleaseExecutionException( "Unable to find phase '" + name + "' to execute" );
320             }
321 
322             updateListener( performRequest.getReleaseManagerListener(), name, PHASE_START );
323 
324             ReleaseResult phaseResult = null;
325             try
326             {
327                 if ( BooleanUtils.isTrue( performRequest.getDryRun() ) )
328                 {
329                     phaseResult = phase.simulate( releaseDescriptor,
330                                                  performRequest.getReleaseEnvironment(),
331                                                  performRequest.getReactorProjects() );
332                 }
333                 else
334                 {
335                     phaseResult = phase.execute( releaseDescriptor,
336                                                  performRequest.getReleaseEnvironment(),
337                                                  performRequest.getReactorProjects() );
338                 }
339             }
340             finally
341             {
342                 if ( result != null && phaseResult != null )
343                 {
344                     result.appendOutput( phaseResult.getOutput() );
345                 }
346             }
347 
348             updateListener( performRequest.getReleaseManagerListener(), name, PHASE_END );
349         }
350 
351         if ( BooleanUtils.isNotFalse( performRequest.getClean() ) )
352         {
353             // call release:clean so that resume will not be possible anymore after a perform
354             clean( performRequest );
355         }
356 
357         updateListener( performRequest.getReleaseManagerListener(), "perform", GOAL_END );
358     }
359 
360     @Override
361     public void branch( ReleaseBranchRequest branchRequest )
362         throws ReleaseExecutionException, ReleaseFailureException
363     {
364         final ReleaseDescriptorBuilder builder = branchRequest.getReleaseDescriptorBuilder();
365         
366         ReleaseUtils.copyPropertiesToReleaseDescriptor( branchRequest.getUserProperties(),
367                     new ReleaseDescriptorBuilder()
368                     {
369                         public ReleaseDescriptorBuilder addDevelopmentVersion( String key,
370                                                                                String value )
371                         {
372                             builder.addDevelopmentVersion( key, value );
373                             return this;
374                         }
375 
376                         public ReleaseDescriptorBuilder addReleaseVersion( String key,
377                                                                            String value )
378                         {
379                             builder.addReleaseVersion( key, value );
380                             return this;
381                         };
382                     } );
383         
384         ReleaseDescriptor releaseDescriptor =
385             loadReleaseDescriptor( builder, branchRequest.getReleaseManagerListener() );
386 
387         boolean dryRun = BooleanUtils.isTrue( branchRequest.getDryRun() );
388 
389         Strategy releaseStrategy = getStrategy( releaseDescriptor.getReleaseStrategyId() );
390 
391         List<String> branchPhases = getGoalPhases( releaseStrategy, "branch" );
392 
393         goalStart( branchRequest.getReleaseManagerListener(), "branch", branchPhases );
394 
395         for ( String name : branchPhases )
396         {
397             ReleasePhase phase = releasePhases.get( name );
398 
399             if ( phase == null )
400             {
401                 throw new ReleaseExecutionException( "Unable to find phase '" + name + "' to execute" );
402             }
403 
404             updateListener( branchRequest.getReleaseManagerListener(), name, PHASE_START );
405 
406             if ( dryRun )
407             {
408                 phase.simulate( releaseDescriptor,
409                                 branchRequest.getReleaseEnvironment(),
410                                 branchRequest.getReactorProjects() );
411             }
412             else // getDryRun is null or FALSE
413             {
414                 phase.execute( releaseDescriptor,
415                                branchRequest.getReleaseEnvironment(),
416                                branchRequest.getReactorProjects() );
417             }
418             updateListener( branchRequest.getReleaseManagerListener(), name, PHASE_END );
419         }
420 
421         if ( !dryRun )
422         {
423             clean( branchRequest );
424         }
425 
426         updateListener( branchRequest.getReleaseManagerListener(), "branch", GOAL_END );
427     }
428 
429     @Override
430     public void updateVersions( ReleaseUpdateVersionsRequest updateVersionsRequest )
431         throws ReleaseExecutionException, ReleaseFailureException
432     {
433         final ReleaseDescriptorBuilder builder = updateVersionsRequest.getReleaseDescriptorBuilder();
434         
435         // Create a config containing values from the session properties (ie command line properties with cli).
436         ReleaseUtils.copyPropertiesToReleaseDescriptor( updateVersionsRequest.getUserProperties(),
437                                     new ReleaseDescriptorBuilder()
438                                     {
439                                         public ReleaseDescriptorBuilder addDevelopmentVersion( String key,
440                                                                                                String value )
441                                         {
442                                             builder.addDevelopmentVersion( key, value );
443                                             return this;
444                                         }
445 
446                                         public ReleaseDescriptorBuilder addReleaseVersion( String key,
447                                                                                            String value )
448                                         {
449                                             builder.addReleaseVersion( key, value );
450                                             return this;
451                                         };
452                                     } );
453 
454         ReleaseDescriptor releaseDescriptor =
455             loadReleaseDescriptor( builder, updateVersionsRequest.getReleaseManagerListener() );
456 
457         Strategy releaseStrategy = getStrategy( releaseDescriptor.getReleaseStrategyId() );
458 
459         List<String> updateVersionsPhases = getGoalPhases( releaseStrategy, "updateVersions" );
460 
461         goalStart( updateVersionsRequest.getReleaseManagerListener(), "updateVersions", updateVersionsPhases );
462 
463         for ( String name : updateVersionsPhases )
464         {
465             ReleasePhase phase = releasePhases.get( name );
466 
467             if ( phase == null )
468             {
469                 throw new ReleaseExecutionException( "Unable to find phase '" + name + "' to execute" );
470             }
471 
472             updateListener( updateVersionsRequest.getReleaseManagerListener(), name, PHASE_START );
473             phase.execute( releaseDescriptor,
474                            updateVersionsRequest.getReleaseEnvironment(),
475                            updateVersionsRequest.getReactorProjects() );
476             updateListener( updateVersionsRequest.getReleaseManagerListener(), name, PHASE_END );
477         }
478 
479         clean( updateVersionsRequest );
480 
481         updateListener( updateVersionsRequest.getReleaseManagerListener(), "updateVersions", GOAL_END );
482     }
483 
484     /**
485      * Determines the path of the working directory. By default, this is the
486      * checkout directory. For some SCMs, the project root directory is not the
487      * checkout directory itself, but a SCM-specific subdirectory.
488      *
489      * @param checkoutDirectory            The checkout directory as java.io.File
490      * @param relativePathProjectDirectory The relative path of the project directory within the checkout
491      *                                     directory or ""
492      * @return The working directory
493      */
494     protected File determineWorkingDirectory( File checkoutDirectory, String relativePathProjectDirectory )
495     {
496         if ( StringUtils.isNotEmpty( relativePathProjectDirectory ) )
497         {
498             return new File( checkoutDirectory, relativePathProjectDirectory );
499         }
500         else
501         {
502             return checkoutDirectory;
503         }
504     }
505 
506     private BuilderReleaseDescriptor loadReleaseDescriptor( ReleaseDescriptorBuilder builder,
507                                                      ReleaseManagerListener listener )
508         throws ReleaseExecutionException
509     {
510         try
511         {
512             updateListener( listener, "verify-release-configuration", PHASE_START );
513             BuilderReleaseDescriptor descriptor = ReleaseUtils.buildReleaseDescriptor( configStore.read( builder ) );
514             updateListener( listener, "verify-release-configuration", PHASE_END );
515             return descriptor;
516         }
517         catch ( ReleaseDescriptorStoreException e )
518         {
519             updateListener( listener, e.getMessage(), ERROR );
520 
521             throw new ReleaseExecutionException( "Error reading stored configuration: " + e.getMessage(), e );
522         }
523     }
524 
525     
526     protected void clean( AbstractReleaseRequest releaseRequest  ) throws ReleaseFailureException
527     {
528         ReleaseCleanRequest cleanRequest = new ReleaseCleanRequest();
529         cleanRequest.setReleaseDescriptorBuilder( releaseRequest.getReleaseDescriptorBuilder() );
530         cleanRequest.setReleaseManagerListener( releaseRequest.getReleaseManagerListener() );
531         cleanRequest.setReactorProjects( releaseRequest.getReactorProjects() );
532 
533         clean( cleanRequest );
534     }
535 
536     @Override
537     public void clean( ReleaseCleanRequest cleanRequest ) throws ReleaseFailureException
538     {
539         updateListener( cleanRequest.getReleaseManagerListener(), "cleanup", PHASE_START );
540 
541         getLogger().info( "Cleaning up after release..." );
542 
543         ReleaseDescriptor releaseDescriptor =
544             ReleaseUtils.buildReleaseDescriptor( cleanRequest.getReleaseDescriptorBuilder() );
545 
546         configStore.delete( releaseDescriptor );
547 
548         Strategy releaseStrategy = getStrategy( releaseDescriptor.getReleaseStrategyId() );
549 
550         Set<String> phases = new LinkedHashSet<>();
551         phases.addAll( getGoalPhases( releaseStrategy, "prepare" ) );
552         phases.addAll( getGoalPhases( releaseStrategy, "branch" ) );
553 
554         for ( String name : phases )
555         {
556             ReleasePhase phase = releasePhases.get( name );
557             
558             if ( phase instanceof ResourceGenerator )
559             {
560                 ( (ResourceGenerator) phase ).clean( cleanRequest.getReactorProjects() );
561             }
562         }
563 
564         updateListener( cleanRequest.getReleaseManagerListener(), "cleanup", PHASE_END );
565     }
566 
567     void setConfigStore( ReleaseDescriptorStore configStore )
568     {
569         this.configStore = configStore;
570     }
571 
572     void goalStart( ReleaseManagerListener listener, String goal, List<String> phases )
573     {
574         if ( listener != null )
575         {
576             listener.goalStart( goal, phases );
577         }
578     }
579 
580     void updateListener( ReleaseManagerListener listener, String name, int state )
581     {
582         if ( listener != null )
583         {
584             switch ( state )
585             {
586                 case GOAL_END:
587                     listener.goalEnd();
588                     break;
589                 case PHASE_SKIP:
590                     listener.phaseSkip( name );
591                     break;
592                 case PHASE_START:
593                     listener.phaseStart( name );
594                     break;
595                 case PHASE_END:
596                     listener.phaseEnd();
597                     break;
598                 default:
599                     listener.error( name );
600             }
601         }
602     }
603 
604     private Strategy getStrategy( String strategyId ) throws ReleaseFailureException
605     {
606         Strategy strategy = strategies.get( strategyId );
607         if ( strategy == null )
608         {
609             throw new ReleaseFailureException( "Unknown strategy: " + strategyId );
610         }
611         return strategy;
612     }
613 
614     private List<String> getGoalPhases( Strategy strategy, String goal )
615     {
616         List<String> phases;
617 
618         if ( "prepare".equals( goal ) )
619         {
620             phases = strategy.getPreparePhases();
621             if ( phases  == null )
622             {
623                 phases = strategies.get( "default" ).getPreparePhases();
624             }
625         }
626         else if ( "perform".equals( goal ) )
627         {
628             phases = strategy.getPerformPhases();
629             if ( phases  == null )
630             {
631                 phases = strategies.get( "default" ).getPerformPhases();
632             }
633         }
634         else if ( "rollback".equals( goal ) )
635         {
636             phases = strategy.getRollbackPhases();
637             if ( phases  == null )
638             {
639                 phases = strategies.get( "default" ).getRollbackPhases();
640             }
641         }
642         else if ( "branch".equals( goal ) )
643         {
644             phases = strategy.getBranchPhases();
645             if ( phases  == null )
646             {
647                 phases = strategies.get( "default" ).getBranchPhases();
648             }
649         }
650         else if ( "updateVersions".equals( goal ) )
651         {
652             phases = strategy.getUpdateVersionsPhases();
653             if ( phases  == null )
654             {
655                 phases = strategies.get( "default" ).getUpdateVersionsPhases();
656             }
657         }
658         else
659         {
660             phases = null;
661         }
662 
663         return Collections.unmodifiableList( phases );
664     }
665 
666     private void logInfo( ReleaseResult result, String message )
667     {
668         if ( result != null )
669         {
670             result.appendInfo( message );
671         }
672 
673         getLogger().info( message );
674     }
675 
676     private void captureException( ReleaseResult result, ReleaseManagerListener listener, Exception e )
677     {
678         updateListener( listener, e.getMessage(), ERROR );
679 
680         result.appendError( e );
681 
682         result.setResultCode( ReleaseResult.ERROR );
683     }
684 }