1 package org.apache.maven;
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.io.IOException;
24 import java.nio.file.Files;
25 import java.nio.file.Path;
26 import java.nio.file.Paths;
27 import java.util.Arrays;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashSet;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Objects;
35 import java.util.Optional;
36 import java.util.function.Function;
37 import java.util.function.Predicate;
38 import java.util.stream.Collectors;
39 import java.util.stream.Stream;
40
41 import org.apache.maven.artifact.ArtifactUtils;
42 import org.apache.maven.execution.MavenSession;
43 import org.apache.maven.model.Model;
44 import org.apache.maven.project.MavenProject;
45 import org.apache.maven.repository.internal.MavenWorkspaceReader;
46 import org.eclipse.aether.artifact.Artifact;
47 import org.eclipse.aether.repository.WorkspaceRepository;
48 import org.eclipse.aether.util.artifact.ArtifactIdUtils;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 import javax.inject.Inject;
53 import javax.inject.Named;
54
55 import static java.util.function.Function.identity;
56 import static java.util.stream.Collectors.groupingBy;
57 import static java.util.stream.Collectors.toMap;
58
59
60
61
62
63
64
65 @Named( ReactorReader.HINT )
66 @SessionScoped
67 class ReactorReader
68 implements MavenWorkspaceReader
69 {
70 public static final String HINT = "reactor";
71
72 private static final Collection<String> COMPILE_PHASE_TYPES =
73 Arrays.asList( "jar", "ejb-client", "war", "rar", "ejb3", "par", "sar", "wsr", "har", "app-client" );
74
75 private static final Logger LOGGER = LoggerFactory.getLogger( ReactorReader.class );
76
77 private final MavenSession session;
78 private final Map<String, MavenProject> projectsByGAV;
79 private final Map<String, List<MavenProject>> projectsByGA;
80 private final WorkspaceRepository repository;
81
82 private Function<MavenProject, String> projectIntoKey =
83 s -> ArtifactUtils.key( s.getGroupId(), s.getArtifactId(), s.getVersion() );
84
85 private Function<MavenProject, String> projectIntoVersionlessKey =
86 s -> ArtifactUtils.versionlessKey( s.getGroupId(), s.getArtifactId() );
87
88 @Inject
89 ReactorReader( MavenSession session )
90 {
91 this.session = session;
92 this.projectsByGAV =
93 session.getAllProjects().stream()
94 .collect( toMap( projectIntoKey, identity() ) );
95
96 this.projectsByGA = projectsByGAV.values().stream()
97 .collect( groupingBy( projectIntoVersionlessKey ) );
98
99 repository = new WorkspaceRepository( "reactor", new HashSet<>( projectsByGAV.keySet() ) );
100 }
101
102
103
104
105
106 public WorkspaceRepository getRepository()
107 {
108 return repository;
109 }
110
111 public File findArtifact( Artifact artifact )
112 {
113 String projectKey = ArtifactUtils.key( artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion() );
114
115 MavenProject project = projectsByGAV.get( projectKey );
116
117 if ( project != null )
118 {
119 File file = find( project, artifact );
120 if ( file == null && project != project.getExecutionProject() )
121 {
122 file = find( project.getExecutionProject(), artifact );
123 }
124 return file;
125 }
126
127 return null;
128 }
129
130 public List<String> findVersions( Artifact artifact )
131 {
132 String key = ArtifactUtils.versionlessKey( artifact.getGroupId(), artifact.getArtifactId() );
133
134 return Optional.ofNullable( projectsByGA.get( key ) )
135 .orElse( Collections.emptyList() ).stream()
136 .filter( s -> Objects.nonNull( find( s, artifact ) ) )
137 .map( MavenProject::getVersion )
138 .collect( Collectors.collectingAndThen( Collectors.toList(), Collections::unmodifiableList ) );
139 }
140
141 @Override
142 public Model findModel( Artifact artifact )
143 {
144 String projectKey = ArtifactUtils.key( artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion() );
145 MavenProject project = projectsByGAV.get( projectKey );
146 return project == null ? null : project.getModel();
147 }
148
149
150
151
152
153 private File find( MavenProject project, Artifact artifact )
154 {
155 if ( "pom".equals( artifact.getExtension() ) )
156 {
157 return project.getFile();
158 }
159
160 Artifact projectArtifact = findMatchingArtifact( project, artifact );
161 File packagedArtifactFile = determinePreviouslyPackagedArtifactFile( project, projectArtifact );
162
163 if ( hasArtifactFileFromPackagePhase( projectArtifact ) )
164 {
165 return projectArtifact.getFile();
166 }
167
168 else if ( packagedArtifactFile != null && packagedArtifactFile.exists()
169 && isPackagedArtifactUpToDate( project, packagedArtifactFile, artifact ) )
170 {
171 return packagedArtifactFile;
172 }
173 else if ( !hasBeenPackagedDuringThisSession( project ) )
174 {
175
176
177 return determineBuildOutputDirectoryForArtifact( project, artifact );
178 }
179
180
181
182 return null;
183 }
184
185 private File determineBuildOutputDirectoryForArtifact( final MavenProject project, final Artifact artifact )
186 {
187 if ( isTestArtifact( artifact ) )
188 {
189 if ( project.hasLifecyclePhase( "test-compile" ) )
190 {
191 return new File( project.getBuild().getTestOutputDirectory() );
192 }
193 }
194 else
195 {
196 String type = artifact.getProperty( "type", "" );
197 File outputDirectory = new File( project.getBuild().getOutputDirectory() );
198
199
200
201 boolean projectCompiledDuringThisSession
202 = project.hasLifecyclePhase( "compile" ) && COMPILE_PHASE_TYPES.contains( type );
203
204
205
206 boolean projectHasOutputFromPreviousSession
207 = !session.getProjects().contains( project ) && outputDirectory.exists();
208
209 if ( projectHasOutputFromPreviousSession || projectCompiledDuringThisSession )
210 {
211 return outputDirectory;
212 }
213 }
214
215
216
217 return null;
218 }
219
220 private File determinePreviouslyPackagedArtifactFile( MavenProject project, Artifact artifact )
221 {
222 if ( artifact == null )
223 {
224 return null;
225 }
226
227 String fileName = String.format( "%s.%s", project.getBuild().getFinalName(), artifact.getExtension() );
228 return new File( project.getBuild().getDirectory(), fileName );
229 }
230
231 private boolean hasArtifactFileFromPackagePhase( Artifact projectArtifact )
232 {
233 return projectArtifact != null && projectArtifact.getFile() != null && projectArtifact.getFile().exists();
234 }
235
236 private boolean isPackagedArtifactUpToDate( MavenProject project, File packagedArtifactFile, Artifact artifact )
237 {
238 Path outputDirectory = Paths.get( project.getBuild().getOutputDirectory() );
239 if ( !outputDirectory.toFile().exists() )
240 {
241 return true;
242 }
243
244 try ( Stream<Path> outputFiles = Files.walk( outputDirectory ) )
245 {
246
247 long artifactLastModified = Files.getLastModifiedTime( packagedArtifactFile.toPath() ).toMillis();
248
249 if ( session.getProjectBuildingRequest().getBuildStartTime() != null )
250 {
251 long buildStartTime = session.getProjectBuildingRequest().getBuildStartTime().getTime();
252 if ( artifactLastModified > buildStartTime )
253 {
254 return true;
255 }
256 }
257
258 Iterator<Path> iterator = outputFiles.iterator();
259 while ( iterator.hasNext() )
260 {
261 Path outputFile = iterator.next();
262
263 if ( Files.isDirectory( outputFile ) )
264 {
265 continue;
266 }
267
268 long outputFileLastModified = Files.getLastModifiedTime( outputFile ).toMillis();
269 if ( outputFileLastModified > artifactLastModified )
270 {
271 File alternative = determineBuildOutputDirectoryForArtifact( project, artifact );
272 if ( alternative != null )
273 {
274 LOGGER.warn( "File '{}' is more recent than the packaged artifact for '{}'; using '{}' instead",
275 relativizeOutputFile( outputFile ), project.getArtifactId(),
276 relativizeOutputFile( alternative.toPath() ) );
277 }
278 else
279 {
280 LOGGER.warn( "File '{}' is more recent than the packaged artifact for '{}'; "
281 + "cannot use the build output directory for this type of artifact",
282 relativizeOutputFile( outputFile ), project.getArtifactId() );
283 }
284 return false;
285 }
286 }
287
288 return true;
289 }
290 catch ( IOException e )
291 {
292 LOGGER.warn( "An I/O error occurred while checking if the packaged artifact is up-to-date "
293 + "against the build output directory. "
294 + "Continuing with the assumption that it is up-to-date.", e );
295 return true;
296 }
297 }
298
299 private boolean hasBeenPackagedDuringThisSession( MavenProject project )
300 {
301 return project.hasLifecyclePhase( "package" ) || project.hasLifecyclePhase( "install" )
302 || project.hasLifecyclePhase( "deploy" );
303 }
304
305 private Path relativizeOutputFile( final Path outputFile )
306 {
307 Path projectBaseDirectory = Paths.get( session.getRequest().getMultiModuleProjectDirectory().toURI() );
308 return projectBaseDirectory.relativize( outputFile );
309 }
310
311
312
313
314
315
316
317
318 private Artifact findMatchingArtifact( MavenProject project, Artifact requestedArtifact )
319 {
320 String requestedRepositoryConflictId = ArtifactIdUtils.toVersionlessId( requestedArtifact );
321
322 Artifact mainArtifact = RepositoryUtils.toArtifact( project.getArtifact() );
323 if ( requestedRepositoryConflictId.equals( ArtifactIdUtils.toVersionlessId( mainArtifact ) ) )
324 {
325 return mainArtifact;
326 }
327
328 return RepositoryUtils.toArtifacts( project.getAttachedArtifacts() ).stream()
329 .filter( isRequestedArtifact( requestedArtifact ) )
330 .findFirst()
331 .orElse( null );
332 }
333
334
335
336
337
338
339
340
341 private Predicate<Artifact> isRequestedArtifact( Artifact requestArtifact )
342 {
343 return s -> s.getArtifactId().equals( requestArtifact.getArtifactId() )
344 && s.getGroupId().equals( requestArtifact.getGroupId() )
345 && s.getVersion().equals( requestArtifact.getVersion() )
346 && s.getExtension().equals( requestArtifact.getExtension() )
347 && s.getClassifier().equals( requestArtifact.getClassifier() );
348
349 }
350
351
352
353
354
355
356
357 private static boolean isTestArtifact( Artifact artifact )
358 {
359 return ( "test-jar".equals( artifact.getProperty( "type", "" ) ) )
360 || ( "jar".equals( artifact.getExtension() ) && "tests".equals( artifact.getClassifier() ) );
361 }
362 }