1 package org.apache.maven.shared.release.phase;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
72
73
74
75 public abstract class AbstractRewritePomsPhase
76 extends AbstractReleasePhase implements ResourceGenerator
77 {
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 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
108
109
110
111 protected final Map<String, ScmTranslator> getScmTranslators()
112 {
113 return scmTranslators;
114 }
115
116
117
118
119
120
121 public void setModelETL( String modelETL )
122 {
123 this.modelETL = modelETL;
124 }
125
126
127
128
129
130
131 public void setStartTime( long startTime )
132 {
133 this.startTime = startTime;
134 }
135
136
137
138
139
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
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
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
381 return;
382 }
383
384 if ( StringUtils.isNumeric( buildOutputTimestamp ) )
385 {
386
387 buildOutputTimestamp = String.valueOf( result.getStartTime() / 1000 );
388 }
389 else if ( buildOutputTimestamp.length() <= 1 )
390 {
391
392 return;
393 }
394 else
395 {
396
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
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
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
477 continue;
478 }
479 }
480 String groupId = ReleaseUtil.interpolate( rawGroupId, projectModel );
481
482 String rawArtifactId = coordinate.getArtifactId();
483 if ( rawArtifactId == null )
484 {
485
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
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
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
542 properties.setProperty( expression, mappedVersion );
543 }
544 else if ( mappedVersion.equals( propertyValue ) )
545 {
546
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
553
554
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
562 }
563 else
564 {
565
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
576
577 throw new ReleaseFailureException( "The version could not be updated: " + rawVersion );
578 }
579 }
580 }
581 else
582 {
583
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
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
625
626
627
628
629
630 protected abstract String getResolvedSnapshotVersion( String artifactVersionlessKey,
631 ReleaseDescriptor releaseDscriptor );
632
633
634
635
636
637
638
639
640
641 protected abstract String getOriginalVersion( ReleaseDescriptor releaseDescriptor, String projectKey,
642 boolean simulate );
643
644
645
646
647
648
649
650
651 protected abstract String getNextVersion( ReleaseDescriptor releaseDescriptor, String key );
652
653
654
655
656
657
658
659
660
661
662
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
671
672
673
674
675 protected boolean isUpdateScm()
676 {
677 return true;
678 }
679
680
681
682
683
684
685
686
687 protected String getOriginalResolvedSnapshotVersion( String artifactVersionlessKey,
688 ReleaseDescriptor releaseDescriptor )
689 {
690 return releaseDescriptor.getDependencyOriginalVersion( artifactVersionlessKey );
691 }
692
693
694
695
696
697
698
699
700
701
702 protected static String translateUrlPath( String trunkPath, String tagPath, String urlPath )
703 {
704 trunkPath = trunkPath.trim();
705 tagPath = tagPath.trim();
706
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
718 int i = 0;
719 while ( ( i < tagPathChars.length ) && ( i < trunkPathChars.length ) && tagPathChars[i] == trunkPathChars[i] )
720 {
721 ++i;
722 }
723
724
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 }