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