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