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