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