1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
71
72
73
74 public abstract class AbstractRewritePomsPhase extends AbstractReleasePhase implements ResourceGenerator {
75
76
77
78 private final ScmRepositoryConfigurator scmRepositoryConfigurator;
79
80 private final Map<String, ModelETLFactory> modelETLFactories;
81
82
83
84
85 private Map<String, ScmTranslator> scmTranslators;
86
87
88
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
105
106
107
108 protected final Map<String, ScmTranslator> getScmTranslators() {
109 return scmTranslators;
110 }
111
112
113
114
115
116
117 public void setModelETL(String modelETL) {
118 this.modelETL = modelETL;
119 }
120
121
122
123
124
125
126 public void setStartTime(long startTime) {
127 this.startTime = startTime;
128 }
129
130
131
132
133
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
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
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
414 return;
415 }
416
417 if (StringUtils.isNumeric(buildOutputTimestamp)) {
418
419 buildOutputTimestamp = String.valueOf(result.getStartTime() / 1000);
420 } else if (buildOutputTimestamp.length() <= 1) {
421
422 return;
423 } else {
424
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
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
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
492 continue;
493 }
494 }
495 String groupId = ReleaseUtil.interpolate(rawGroupId, projectModel);
496
497 String rawArtifactId = coordinate.getArtifactId();
498 if (rawArtifactId == null) {
499
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
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
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
545 properties.setProperty(expression, mappedVersion);
546 } else if (mappedVersion.equals(propertyValue)) {
547
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
554
555
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
563 } else {
564
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
573
574 throw new ReleaseFailureException("The version could not be updated: " + rawVersion);
575 }
576 }
577 } else {
578
579 }
580 } else if (resolvedSnapshotVersion != null) {
581 logInfo(result, " Updating " + artifactId + " to " + resolvedSnapshotVersion);
582
583 coordinate.setVersion(resolvedSnapshotVersion);
584 } else {
585
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
609
610
611
612
613
614 protected abstract String getResolvedSnapshotVersion(
615 String artifactVersionlessKey, ReleaseDescriptor releaseDscriptor);
616
617
618
619
620
621
622
623
624
625 protected abstract String getOriginalVersion(
626 ReleaseDescriptor releaseDescriptor, String projectKey, boolean simulate);
627
628
629
630
631
632
633
634
635 protected abstract String getNextVersion(ReleaseDescriptor releaseDescriptor, String key);
636
637
638
639
640
641
642
643
644
645
646
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
659
660
661
662
663 protected boolean isUpdateScm() {
664 return true;
665 }
666
667
668
669
670
671
672
673
674 protected String getOriginalResolvedSnapshotVersion(
675 String artifactVersionlessKey, ReleaseDescriptor releaseDescriptor) {
676 return releaseDescriptor.getDependencyOriginalVersion(artifactVersionlessKey);
677 }
678
679
680
681
682
683
684
685
686
687
688 protected static String translateUrlPath(String trunkPath, String tagPath, String urlPath) {
689 trunkPath = trunkPath.trim();
690 tagPath = tagPath.trim();
691
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
701 int i = 0;
702 while ((i < tagPathChars.length) && (i < trunkPathChars.length) && tagPathChars[i] == trunkPathChars[i]) {
703 ++i;
704 }
705
706
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 }