View Javadoc
1   package org.apache.maven.shared.release.phase;
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.net.URI;
24  import java.text.DateFormat;
25  import java.text.SimpleDateFormat;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Date;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Properties;
32  import java.util.TimeZone;
33  
34  import org.apache.maven.artifact.Artifact;
35  import org.apache.maven.artifact.ArtifactUtils;
36  import org.apache.maven.model.Build;
37  import org.apache.maven.model.BuildBase;
38  import org.apache.maven.model.Model;
39  import org.apache.maven.model.ModelBase;
40  import org.apache.maven.model.Plugin;
41  import org.apache.maven.model.Profile;
42  import org.apache.maven.project.MavenProject;
43  import org.apache.maven.scm.ScmException;
44  import org.apache.maven.scm.ScmFileSet;
45  import org.apache.maven.scm.command.edit.EditScmResult;
46  import org.apache.maven.scm.manager.NoSuchScmProviderException;
47  import org.apache.maven.scm.provider.ScmProvider;
48  import org.apache.maven.scm.repository.ScmRepository;
49  import org.apache.maven.scm.repository.ScmRepositoryException;
50  import org.apache.maven.shared.release.ReleaseExecutionException;
51  import org.apache.maven.shared.release.ReleaseFailureException;
52  import org.apache.maven.shared.release.ReleaseResult;
53  import org.apache.maven.shared.release.config.ReleaseDescriptor;
54  import org.apache.maven.shared.release.env.ReleaseEnvironment;
55  import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
56  import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
57  import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
58  import org.apache.maven.shared.release.scm.ScmTranslator;
59  import org.apache.maven.shared.release.transform.MavenCoordinate;
60  import org.apache.maven.shared.release.transform.ModelETL;
61  import org.apache.maven.shared.release.transform.ModelETLFactory;
62  import org.apache.maven.shared.release.transform.ModelETLRequest;
63  import org.apache.maven.shared.release.transform.jdom2.JDomModelETLFactory;
64  import org.apache.maven.shared.release.util.ReleaseUtil;
65  import org.codehaus.plexus.util.StringUtils;
66  
67  import static java.util.Objects.requireNonNull;
68  import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
69  
70  /**
71   * Base class for rewriting phases.
72   *
73   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
74   */
75  public abstract class AbstractRewritePomsPhase
76          extends AbstractReleasePhase implements ResourceGenerator
77  {
78      /**
79       * Tool that gets a configured SCM repository from release configuration.
80       */
81      private final ScmRepositoryConfigurator scmRepositoryConfigurator;
82  
83      private final Map<String, ModelETLFactory> modelETLFactories;
84  
85      /**
86       * SCM URL translators mapped by provider name.
87       */
88      private Map<String, ScmTranslator> scmTranslators;
89  
90      /**
91       * Use jdom2-sax as default
92       */
93      private String modelETL = JDomModelETLFactory.NAME;
94  
95      private long startTime = -1 * 1000;
96  
97      protected AbstractRewritePomsPhase( ScmRepositoryConfigurator scmRepositoryConfigurator,
98                                          Map<String, ModelETLFactory> modelETLFactories,
99                                          Map<String, ScmTranslator> scmTranslators )
100     {
101         this.scmRepositoryConfigurator = requireNonNull( scmRepositoryConfigurator );
102         this.modelETLFactories = requireNonNull( modelETLFactories );
103         this.scmTranslators = requireNonNull( scmTranslators );
104     }
105 
106     /**
107      * <p>Getter for the field <code>scmTranslators</code>.</p>
108      *
109      * @return a {@link java.util.Map} object
110      */
111     protected final Map<String, ScmTranslator> getScmTranslators()
112     {
113         return scmTranslators;
114     }
115 
116     /**
117      * <p>Setter for the field <code>modelETL</code>.</p>
118      *
119      * @param modelETL a {@link java.lang.String} object
120      */
121     public void setModelETL( String modelETL )
122     {
123         this.modelETL = modelETL;
124     }
125 
126     /**
127      * <p>Setter for the field <code>startTime</code>.</p>
128      *
129      * @param startTime a long
130      */
131     public void setStartTime( long startTime )
132     {
133         this.startTime = startTime;
134     }
135 
136     /**
137      * <p>getPomSuffix.</p>
138      *
139      * @return a {@link java.lang.String} object
140      */
141     protected abstract String getPomSuffix();
142 
143     @Override
144     public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
145                                   List<MavenProject> reactorProjects )
146             throws ReleaseExecutionException, ReleaseFailureException
147     {
148         ReleaseResult result = new ReleaseResult();
149 
150         transform( releaseDescriptor, releaseEnvironment, reactorProjects, false, result );
151 
152         result.setResultCode( ReleaseResult.SUCCESS );
153 
154         return result;
155     }
156 
157     @Override
158     public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
159                                    List<MavenProject> reactorProjects )
160             throws ReleaseExecutionException, ReleaseFailureException
161     {
162         ReleaseResult result = new ReleaseResult();
163 
164         transform( releaseDescriptor, releaseEnvironment, reactorProjects, true, result );
165 
166         result.setResultCode( ReleaseResult.SUCCESS );
167 
168         return result;
169     }
170 
171     @Override
172     public ReleaseResult clean( List<MavenProject> reactorProjects )
173     {
174         ReleaseResult result = new ReleaseResult();
175 
176         if ( reactorProjects != null )
177         {
178             for ( MavenProject project : reactorProjects )
179             {
180                 File pomFile = ReleaseUtil.getStandardPom( project );
181                 // MRELEASE-273 : if no pom
182                 if ( pomFile != null )
183                 {
184                     File file = new File( pomFile.getParentFile(), pomFile.getName() + "." + getPomSuffix() );
185                     if ( file.exists() )
186                     {
187                         file.delete();
188                     }
189                 }
190             }
191         }
192 
193         result.setResultCode( ReleaseResult.SUCCESS );
194 
195         return result;
196     }
197 
198     private void transform( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
199                             List<MavenProject> reactorProjects, boolean simulate, ReleaseResult result )
200             throws ReleaseExecutionException, ReleaseFailureException
201     {
202         result.setStartTime( ( startTime >= 0 ) ? startTime : System.currentTimeMillis() );
203 
204         URI root = ReleaseUtil.getRootProject( reactorProjects ).getBasedir().toURI();
205 
206         for ( MavenProject project : reactorProjects )
207         {
208             URI pom = project.getFile().toURI();
209             logInfo( result,
210                      "Transforming " + root.relativize( pom ).getPath() + ' '
211                          + buffer().project( project.getArtifactId() ) + " '" + project.getName() + "'"
212                          + ( simulate ? " with ." + getPomSuffix() + " suffix" : "" ) + "..." );
213 
214             transformProject( project, releaseDescriptor, releaseEnvironment, simulate, result );
215         }
216     }
217 
218     private void transformProject( MavenProject project, ReleaseDescriptor releaseDescriptor,
219                                    ReleaseEnvironment releaseEnvironment, boolean simulate,
220                                    ReleaseResult result )
221             throws ReleaseExecutionException, ReleaseFailureException
222     {
223         File pomFile = ReleaseUtil.getStandardPom( project );
224 
225         ModelETLRequest request = new ModelETLRequest();
226         request.setProject( project );
227         request.setReleaseDescriptor( releaseDescriptor );
228 
229         ModelETL etl = modelETLFactories.get( modelETL ).newInstance( request );
230 
231         etl.extract( pomFile );
232 
233         ScmRepository scmRepository = null;
234         ScmProvider provider = null;
235 
236         if ( isUpdateScm() )
237         {
238             try
239             {
240                 scmRepository = scmRepositoryConfigurator.getConfiguredRepository( releaseDescriptor,
241                         releaseEnvironment.getSettings() );
242 
243                 provider = scmRepositoryConfigurator.getRepositoryProvider( scmRepository );
244             }
245             catch ( ScmRepositoryException e )
246             {
247                 throw new ReleaseScmRepositoryException( e.getMessage(), e.getValidationMessages() );
248             }
249             catch ( NoSuchScmProviderException e )
250             {
251                 throw new ReleaseExecutionException( "Unable to configure SCM repository: " + e.getMessage(), e );
252             }
253         }
254 
255         transformDocument( project, etl.getModel(), releaseDescriptor, scmRepository, result,
256                 simulate );
257 
258         File outputFile;
259         if ( simulate )
260         {
261             outputFile = new File( pomFile.getParentFile(), pomFile.getName() + "." + getPomSuffix() );
262         }
263         else
264         {
265             outputFile = pomFile;
266             prepareScm( pomFile, releaseDescriptor, scmRepository, provider );
267         }
268         etl.load( outputFile );
269 
270     }
271 
272     private void transformDocument( MavenProject project, Model modelTarget, ReleaseDescriptor releaseDescriptor,
273                                     ScmRepository scmRepository, ReleaseResult result,
274                                     boolean simulate )
275             throws ReleaseExecutionException, ReleaseFailureException
276     {
277         Model model = project.getModel();
278 
279         Properties properties = modelTarget.getProperties();
280 
281         String parentVersion = rewriteParent( project, modelTarget, releaseDescriptor, simulate );
282 
283         String projectId = ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() );
284 
285         rewriteVersion( modelTarget, releaseDescriptor, projectId, project );
286 
287         Build buildTarget = modelTarget.getBuild();
288         if ( buildTarget != null )
289         {
290             // profile.build.extensions doesn't exist, so only rewrite project.build.extensions
291             rewriteArtifactVersions( toMavenCoordinates( buildTarget.getExtensions() ),
292                     model, properties, result, releaseDescriptor, simulate );
293 
294             rewriteArtifactVersions( toMavenCoordinates( buildTarget.getPlugins() ),
295                     model, properties, result, releaseDescriptor, simulate );
296 
297             for ( Plugin plugin : buildTarget.getPlugins() )
298             {
299                 rewriteArtifactVersions( toMavenCoordinates( plugin.getDependencies() ),
300                         model, properties,
301                         result, releaseDescriptor, simulate );
302             }
303 
304             if ( buildTarget.getPluginManagement() != null )
305             {
306                 rewriteArtifactVersions( toMavenCoordinates( buildTarget.getPluginManagement().getPlugins() ), model,
307                         properties, result, releaseDescriptor, simulate );
308 
309                 for ( Plugin plugin : buildTarget.getPluginManagement().getPlugins() )
310                 {
311                     rewriteArtifactVersions( toMavenCoordinates( plugin.getDependencies() ), model, properties, result,
312                             releaseDescriptor, simulate );
313                 }
314             }
315         }
316 
317         for ( Profile profile : modelTarget.getProfiles() )
318         {
319             BuildBase profileBuild = profile.getBuild();
320             if ( profileBuild != null )
321             {
322                 rewriteArtifactVersions( toMavenCoordinates( profileBuild.getPlugins() ), model, properties, result,
323                         releaseDescriptor, simulate );
324 
325                 for ( Plugin plugin : profileBuild.getPlugins() )
326                 {
327                     rewriteArtifactVersions( toMavenCoordinates( plugin.getDependencies() ), model, properties, result,
328                             releaseDescriptor, simulate );
329                 }
330 
331                 if ( profileBuild.getPluginManagement() != null )
332                 {
333                     rewriteArtifactVersions( toMavenCoordinates( profileBuild.getPluginManagement().getPlugins() ),
334                             model, properties, result, releaseDescriptor, simulate );
335 
336                     for ( Plugin plugin : profileBuild.getPluginManagement().getPlugins() )
337                     {
338                         rewriteArtifactVersions( toMavenCoordinates( plugin.getDependencies() ), model, properties,
339                                 result, releaseDescriptor, simulate );
340                     }
341                 }
342             }
343         }
344 
345         List<ModelBase> modelBases = new ArrayList<>();
346         modelBases.add( modelTarget );
347         modelBases.addAll( modelTarget.getProfiles() );
348 
349         for ( ModelBase modelBase : modelBases )
350         {
351             rewriteArtifactVersions( toMavenCoordinates( modelBase.getDependencies() ), model, properties, result,
352                     releaseDescriptor, simulate );
353 
354             if ( modelBase.getDependencyManagement() != null )
355             {
356                 rewriteArtifactVersions( toMavenCoordinates( modelBase.getDependencyManagement().getDependencies() ),
357                         model, properties, result, releaseDescriptor, simulate );
358             }
359 
360             if ( modelBase.getReporting() != null )
361             {
362                 rewriteArtifactVersions( toMavenCoordinates( modelBase.getReporting().getPlugins() ), model, properties,
363                         result, releaseDescriptor, simulate );
364             }
365         }
366 
367         transformScm( project, modelTarget, releaseDescriptor, projectId, scmRepository, result );
368 
369         if ( properties != null )
370         {
371             rewriteBuildOutputTimestampProperty( properties, result );
372         }
373     }
374 
375     private void rewriteBuildOutputTimestampProperty( Properties properties, ReleaseResult result )
376     {
377         String buildOutputTimestamp = properties.getProperty( "project.build.outputTimestamp" );
378         if ( buildOutputTimestamp == null || StringUtils.isEmpty( buildOutputTimestamp ) )
379         {
380             // no Reproducible Builds output timestamp defined
381             return;
382         }
383 
384         if ( StringUtils.isNumeric( buildOutputTimestamp ) )
385         {
386             // int representing seconds since the epoch, like SOURCE_DATE_EPOCH
387             buildOutputTimestamp = String.valueOf( result.getStartTime() / 1000 );
388         }
389         else if ( buildOutputTimestamp.length() <= 1 )
390         {
391             // value length == 1 means disable Reproducible Builds
392             return;
393         }
394         else
395         {
396             // ISO-8601
397             DateFormat df = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss'Z'" );
398             df.setTimeZone( TimeZone.getTimeZone( "UTC" ) );
399             buildOutputTimestamp = df.format( new Date( result.getStartTime() ) );
400         }
401         properties.setProperty( "project.build.outputTimestamp", buildOutputTimestamp );
402     }
403 
404     private void rewriteVersion( Model modelTarget, ReleaseDescriptor releaseDescriptor, String projectId,
405                                  MavenProject project )
406             throws ReleaseFailureException
407     {
408         String version = getNextVersion( releaseDescriptor, projectId );
409         if ( version == null )
410         {
411             throw new ReleaseFailureException( "Version for '" + project.getName() + "' was not mapped" );
412         }
413 
414         modelTarget.setVersion( version );
415     }
416 
417     private String rewriteParent( MavenProject project, Model targetModel,
418                                   ReleaseDescriptor releaseDescriptor, boolean simulate )
419             throws ReleaseFailureException
420     {
421         String parentVersion = null;
422         if ( project.hasParent() )
423         {
424             MavenProject parent = project.getParent();
425             String key = ArtifactUtils.versionlessKey( parent.getGroupId(), parent.getArtifactId() );
426             parentVersion = getNextVersion( releaseDescriptor, key );
427             if ( parentVersion == null )
428             {
429                 //MRELEASE-317
430                 parentVersion = getResolvedSnapshotVersion( key, releaseDescriptor );
431             }
432             if ( parentVersion == null )
433             {
434                 String original = getOriginalVersion( releaseDescriptor, key, simulate );
435                 if ( parent.getVersion().equals( original ) )
436                 {
437                     throw new ReleaseFailureException( "Version for parent '" + parent.getName() + "' was not mapped" );
438                 }
439             }
440             else
441             {
442                 targetModel.getParent().setVersion( parentVersion );
443             }
444         }
445         return parentVersion;
446     }
447 
448     private void rewriteArtifactVersions( Collection<MavenCoordinate> elements, Model projectModel,
449                                           Properties properties, ReleaseResult result,
450                                           ReleaseDescriptor releaseDescriptor, boolean simulate )
451             throws ReleaseExecutionException, ReleaseFailureException
452     {
453         if ( elements == null )
454         {
455             return;
456         }
457         String projectId = ArtifactUtils.versionlessKey( projectModel.getGroupId(), projectModel.getArtifactId() );
458         for ( MavenCoordinate coordinate : elements )
459         {
460             String rawVersion = coordinate.getVersion();
461             if ( rawVersion == null )
462             {
463                 // managed dependency or unversioned plugin
464                 continue;
465             }
466 
467             String rawGroupId = coordinate.getGroupId();
468             if ( rawGroupId == null )
469             {
470                 if ( "plugin".equals( coordinate.getName() ) )
471                 {
472                     rawGroupId = "org.apache.maven.plugins";
473                 }
474                 else
475                 {
476                     // incomplete dependency
477                     continue;
478                 }
479             }
480             String groupId = ReleaseUtil.interpolate( rawGroupId, projectModel );
481 
482             String rawArtifactId = coordinate.getArtifactId();
483             if ( rawArtifactId == null )
484             {
485                 // incomplete element
486                 continue;
487             }
488             String artifactId = ReleaseUtil.interpolate( rawArtifactId, projectModel );
489 
490             String key = ArtifactUtils.versionlessKey( groupId, artifactId );
491             String resolvedSnapshotVersion = getResolvedSnapshotVersion( key, releaseDescriptor );
492             String mappedVersion = getNextVersion( releaseDescriptor, key );
493             String originalVersion = getOriginalVersion( releaseDescriptor, key, simulate );
494             if ( originalVersion == null )
495             {
496                 originalVersion = getOriginalResolvedSnapshotVersion( key, releaseDescriptor );
497             }
498 
499             // MRELEASE-220
500             if ( mappedVersion != null && mappedVersion.endsWith( Artifact.SNAPSHOT_VERSION )
501                     && !rawVersion.endsWith( Artifact.SNAPSHOT_VERSION ) && !releaseDescriptor.isUpdateDependencies() )
502             {
503                 continue;
504             }
505 
506             if ( mappedVersion != null )
507             {
508                 if ( rawVersion.equals( originalVersion ) )
509                 {
510                     logInfo( result, "  Updating " + artifactId + " to " + mappedVersion );
511                     coordinate.setVersion( mappedVersion );
512                 }
513                 else if ( rawVersion.matches( "\\$\\{.+\\}" ) )
514                 {
515                     String expression = rawVersion.substring( 2, rawVersion.length() - 1 );
516 
517                     if ( expression.startsWith( "project." ) || expression.startsWith( "pom." )
518                             || "version".equals( expression ) )
519                     {
520                         if ( !mappedVersion.equals( getNextVersion( releaseDescriptor, projectId ) ) )
521                         {
522                             logInfo( result, "  Updating " + artifactId + " to " + mappedVersion );
523                             coordinate.setVersion( mappedVersion );
524                         }
525                         else
526                         {
527                             logInfo( result, "  Ignoring artifact version update for expression " + rawVersion );
528                         }
529                     }
530                     else if ( properties != null )
531                     {
532                         // version is an expression, check for properties to update instead
533 
534                         String propertyValue = properties.getProperty( expression );
535 
536                         if ( propertyValue != null )
537                         {
538                             if ( propertyValue.equals( originalVersion ) )
539                             {
540                                 logInfo( result, "  Updating " + rawVersion + " to " + mappedVersion );
541                                 // change the property only if the property is the same as what's in the reactor
542                                 properties.setProperty( expression, mappedVersion );
543                             }
544                             else if ( mappedVersion.equals( propertyValue ) )
545                             {
546                                 // this property may have been updated during processing a sibling.
547                                 logInfo( result, "  Ignoring artifact version update for expression " + rawVersion
548                                         + " because it is already updated" );
549                             }
550                             else if ( !mappedVersion.equals( rawVersion ) )
551                             {
552                                 // WARNING: ${pom.*} prefix support and ${version} is about to be dropped in mvn4!
553                                 // https://issues.apache.org/jira/browse/MNG-7404
554                                 // https://issues.apache.org/jira/browse/MNG-7244
555                                 if ( mappedVersion.matches( "\\$\\{project.+\\}" )
556                                         || mappedVersion.matches( "\\$\\{pom.+\\}" )
557                                         || "${version}".equals( mappedVersion ) )
558                                 {
559                                     logInfo( result, "  Ignoring artifact version update for expression "
560                                             + mappedVersion );
561                                     // ignore... we cannot update this expression
562                                 }
563                                 else
564                                 {
565                                     // the value of the expression conflicts with what the user wanted to release
566                                     throw new ReleaseFailureException( "The artifact (" + key + ") requires a "
567                                             + "different version (" + mappedVersion + ") than what is found ("
568                                             + propertyValue + ") for the expression (" + expression + ") in the "
569                                             + "project (" + projectId + ")." );
570                                 }
571                             }
572                         }
573                         else
574                         {
575                             // the expression used to define the version of this artifact may be inherited
576                             // TODO needs a better error message, what pom? what dependency?
577                             throw new ReleaseFailureException( "The version could not be updated: " + rawVersion );
578                         }
579                     }
580                 }
581                 else
582                 {
583                     // different/previous version not related to current release
584                 }
585             }
586             else if ( resolvedSnapshotVersion != null )
587             {
588                 logInfo( result, "  Updating " + artifactId + " to " + resolvedSnapshotVersion );
589 
590                 coordinate.setVersion( resolvedSnapshotVersion );
591             }
592             else
593             {
594                 // artifact not related to current release
595             }
596         }
597     }
598 
599     private void prepareScm( File pomFile, ReleaseDescriptor releaseDescriptor, ScmRepository repository,
600                              ScmProvider provider )
601             throws ReleaseExecutionException, ReleaseScmCommandException
602     {
603         try
604         {
605             if ( isUpdateScm() && ( releaseDescriptor.isScmUseEditMode() || provider.requiresEditMode() ) )
606             {
607                 EditScmResult result = provider.edit( repository, new ScmFileSet(
608                         new File( releaseDescriptor.getWorkingDirectory() ), pomFile ) );
609 
610                 if ( !result.isSuccess() )
611                 {
612                     throw new ReleaseScmCommandException( "Unable to enable editing on the POM", result );
613                 }
614             }
615         }
616         catch ( ScmException e )
617         {
618             throw new ReleaseExecutionException( "An error occurred enabling edit mode: " + e.getMessage(), e );
619         }
620     }
621 
622 
623     /**
624      * <p>getResolvedSnapshotVersion.</p>
625      *
626      * @param artifactVersionlessKey a {@link java.lang.String} object
627      * @param releaseDscriptor       a {@link org.apache.maven.shared.release.config.ReleaseDescriptor} object
628      * @return a {@link java.lang.String} object
629      */
630     protected abstract String getResolvedSnapshotVersion( String artifactVersionlessKey,
631                                                           ReleaseDescriptor releaseDscriptor );
632 
633     /**
634      * <p>getOriginalVersion.</p>
635      *
636      * @param releaseDescriptor a {@link org.apache.maven.shared.release.config.ReleaseDescriptor} object
637      * @param projectKey        a {@link java.lang.String} object
638      * @param simulate          a boolean
639      * @return a {@link java.lang.String} object
640      */
641     protected abstract String getOriginalVersion( ReleaseDescriptor releaseDescriptor, String projectKey,
642                                                   boolean simulate );
643 
644     /**
645      * <p>getNextVersion.</p>
646      *
647      * @param releaseDescriptor a {@link org.apache.maven.shared.release.config.ReleaseDescriptor} object
648      * @param key               a {@link java.lang.String} object
649      * @return a {@link java.lang.String} object
650      */
651     protected abstract String getNextVersion( ReleaseDescriptor releaseDescriptor, String key );
652 
653     /**
654      * <p>transformScm.</p>
655      *
656      * @param project           a {@link org.apache.maven.project.MavenProject} object
657      * @param modelTarget       a {@link org.apache.maven.model.Model} object
658      * @param releaseDescriptor a {@link org.apache.maven.shared.release.config.ReleaseDescriptor} object
659      * @param projectId         a {@link java.lang.String} object
660      * @param scmRepository     a {@link org.apache.maven.scm.repository.ScmRepository} object
661      * @param result            a {@link org.apache.maven.shared.release.ReleaseResult} object
662      * @throws org.apache.maven.shared.release.ReleaseExecutionException if any.
663      */
664     protected abstract void transformScm( MavenProject project, Model modelTarget, ReleaseDescriptor releaseDescriptor,
665                                           String projectId, ScmRepository scmRepository,
666                                           ReleaseResult result )
667             throws ReleaseExecutionException;
668 
669     /**
670      * <p>isUpdateScm.</p>
671      *
672      * @return {@code true} if the SCM-section should be updated, otherwise {@code false}
673      * @since 2.4
674      */
675     protected boolean isUpdateScm()
676     {
677         return true;
678     }
679 
680     /**
681      * <p>getOriginalResolvedSnapshotVersion.</p>
682      *
683      * @param artifactVersionlessKey a {@link java.lang.String} object
684      * @param releaseDescriptor      a {@link org.apache.maven.shared.release.config.ReleaseDescriptor} object
685      * @return a {@link java.lang.String} object
686      */
687     protected String getOriginalResolvedSnapshotVersion( String artifactVersionlessKey,
688                                                          ReleaseDescriptor releaseDescriptor )
689     {
690         return releaseDescriptor.getDependencyOriginalVersion( artifactVersionlessKey );
691     }
692 
693     /**
694      * Determines the relative path from trunk to tag, and adds this relative path
695      * to the url.
696      *
697      * @param trunkPath - The trunk url
698      * @param tagPath   - The tag base
699      * @param urlPath   - scm.url or scm.connection
700      * @return The url path for the tag.
701      */
702     protected static String translateUrlPath( String trunkPath, String tagPath, String urlPath )
703     {
704         trunkPath = trunkPath.trim();
705         tagPath = tagPath.trim();
706         //Strip the slash at the end if one is present
707         if ( trunkPath.endsWith( "/" ) )
708         {
709             trunkPath = trunkPath.substring( 0, trunkPath.length() - 1 );
710         }
711         if ( tagPath.endsWith( "/" ) )
712         {
713             tagPath = tagPath.substring( 0, tagPath.length() - 1 );
714         }
715         char[] tagPathChars = trunkPath.toCharArray();
716         char[] trunkPathChars = tagPath.toCharArray();
717         // Find the common path between trunk and tags
718         int i = 0;
719         while ( ( i < tagPathChars.length ) && ( i < trunkPathChars.length ) && tagPathChars[i] == trunkPathChars[i] )
720         {
721             ++i;
722         }
723         // If there is nothing common between trunk and tags, or the relative
724         // path does not exist in the url, then just return the tag.
725         if ( i == 0 || urlPath.indexOf( trunkPath.substring( i ) ) < 0 )
726         {
727             return tagPath;
728         }
729         else
730         {
731             return StringUtils.replace( urlPath, trunkPath.substring( i ), tagPath.substring( i ) );
732         }
733     }
734 
735     private Collection<MavenCoordinate> toMavenCoordinates( List<?> objects )
736     {
737         Collection<MavenCoordinate> coordinates = new ArrayList<>( objects.size() );
738         for ( Object object : objects )
739         {
740             if ( object instanceof MavenCoordinate )
741             {
742                 coordinates.add( (MavenCoordinate) object );
743             }
744             else
745             {
746                 throw new UnsupportedOperationException();
747             }
748         }
749         return coordinates;
750     }
751 
752 
753 }