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