View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.internal.aether;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.UncheckedIOException;
24  import java.nio.charset.StandardCharsets;
25  import java.nio.file.Files;
26  import java.nio.file.Path;
27  import java.util.ArrayList;
28  import java.util.List;
29  import java.util.ListIterator;
30  import java.util.Objects;
31  
32  import org.apache.maven.model.InputLocation;
33  import org.apache.maven.model.Plugin;
34  import org.eclipse.aether.AbstractRepositoryListener;
35  import org.eclipse.aether.RepositoryEvent;
36  import org.eclipse.aether.RepositorySystemSession;
37  import org.eclipse.aether.RequestTrace;
38  import org.eclipse.aether.artifact.Artifact;
39  import org.eclipse.aether.collection.CollectStepData;
40  import org.eclipse.aether.graph.Dependency;
41  import org.eclipse.aether.graph.DependencyNode;
42  import org.eclipse.aether.repository.RemoteRepository;
43  import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
44  import org.eclipse.aether.resolution.ArtifactRequest;
45  import org.eclipse.aether.util.artifact.ArtifactIdUtils;
46  
47  import static java.util.Objects.requireNonNull;
48  
49  /**
50   * A class building reverse tree using {@link CollectStepData} trace data provided in {@link RepositoryEvent}
51   * events fired during collection.
52   *
53   * @since 3.9.0
54   */
55  class ReverseTreeRepositoryListener extends AbstractRepositoryListener {
56      @Override
57      public void artifactResolved(RepositoryEvent event) {
58          requireNonNull(event, "event cannot be null");
59  
60          if (!isLocalRepositoryArtifactOrMissing(event.getSession(), event.getArtifact())) {
61              return;
62          }
63  
64          RequestTrace trace = event.getTrace();
65  
66          CollectStepData collectStepTrace = null;
67          ArtifactRequest artifactRequest = null;
68          ArtifactDescriptorRequest artifactDescriptorRequest = null;
69          Plugin plugin = null;
70  
71          while (trace != null) {
72              Object data = trace.getData();
73              if (data instanceof CollectStepData) {
74                  collectStepTrace = (CollectStepData) data;
75              } else if (data instanceof ArtifactDescriptorRequest) {
76                  artifactDescriptorRequest = (ArtifactDescriptorRequest) data;
77              } else if (data instanceof ArtifactRequest) {
78                  artifactRequest = (ArtifactRequest) data;
79              } else if (data instanceof Plugin) {
80                  plugin = (Plugin) data;
81              }
82              trace = trace.getParent();
83          }
84  
85          Path trackingDir;
86          boolean missing = event.getFile() == null;
87          if (missing) {
88              // missing artifact - let's track the path anyway
89              File dir = event.getSession().getLocalRepository().getBasedir();
90              dir = new File(
91                      dir, event.getSession().getLocalRepositoryManager().getPathForLocalArtifact(event.getArtifact()));
92              trackingDir = dir.getParentFile().toPath().resolve(".tracking");
93          } else {
94              trackingDir = event.getFile().getParentFile().toPath().resolve(".tracking");
95          }
96  
97          String baseName;
98          String ext = missing ? ".miss" : ".dep";
99          Path trackingFile = null;
100 
101         String indent = "";
102         ArrayList<String> trackingData = new ArrayList<>();
103 
104         if (collectStepTrace == null && plugin != null) {
105             ext = ".plugin";
106             baseName = plugin.getGroupId() + "_" + plugin.getArtifactId() + "_" + plugin.getVersion();
107             trackingFile = trackingDir.resolve(baseName + ext);
108             if (Files.exists(trackingFile)) {
109                 return;
110             }
111 
112             if (event.getArtifact() != null) {
113                 trackingData.add(indent + event.getArtifact());
114                 indent += "  ";
115             }
116             trackingData.add(indent + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":" + plugin.getVersion());
117             indent += "  ";
118 
119             InputLocation location = plugin.getLocation("");
120             if (location != null && location.getSource() != null) {
121                 trackingData.add(indent + location.getSource().getModelId() + " (implicit)");
122                 indent += "  ";
123             }
124         } else if (collectStepTrace != null) {
125             if (collectStepTrace.getPath().get(0).getArtifact() == null) {
126                 return;
127             }
128             baseName = ArtifactIdUtils.toId(collectStepTrace.getPath().get(0).getArtifact())
129                     .replace(":", "_");
130             trackingFile = trackingDir.resolve(baseName + ext);
131             if (Files.exists(trackingFile)) {
132                 return;
133             }
134 
135             Artifact resolvedArtifact = event.getArtifact();
136             Artifact nodeArtifact = collectStepTrace.getNode().getArtifact();
137 
138             if (isInScope(resolvedArtifact, nodeArtifact) || "pom".equals(resolvedArtifact.getExtension())) {
139                 Dependency node = collectStepTrace.getNode();
140                 trackingData.add(resolvedArtifact.toString());
141                 indent += "  ";
142                 trackingData.add(indent + node + " (" + collectStepTrace.getContext() + ")");
143                 ListIterator<DependencyNode> iter = collectStepTrace
144                         .getPath()
145                         .listIterator(collectStepTrace.getPath().size());
146                 while (iter.hasPrevious()) {
147                     DependencyNode curr = iter.previous();
148                     indent += "  ";
149                     trackingData.add(indent + curr + " (" + collectStepTrace.getContext() + ")");
150                 }
151             }
152         }
153 
154         if (trackingFile == null) {
155             return;
156         }
157         try {
158             Files.createDirectories(trackingDir);
159 
160             trackingData.add("");
161             if (!missing) {
162                 if (event.getRepository() != null) {
163                     trackingData.add("Repository: " + event.getRepository());
164                 }
165             } else {
166                 List<RemoteRepository> repositories = new ArrayList<>();
167                 if (artifactRequest != null && artifactRequest.getRepositories() != null) {
168                     repositories.addAll(artifactRequest.getRepositories());
169                 } else if (artifactDescriptorRequest != null && artifactDescriptorRequest.getRepositories() != null) {
170                     repositories.addAll(artifactDescriptorRequest.getRepositories());
171                 }
172                 if (!repositories.isEmpty()) {
173                     trackingData.add("Configured repositories:");
174                     for (RemoteRepository r : repositories) {
175                         trackingData.add(" - " + r.getId() + " : " + r.getUrl());
176                     }
177                 } else {
178                     trackingData.add("No repositories configured");
179                 }
180             }
181 
182             Files.write(trackingFile, trackingData, StandardCharsets.UTF_8);
183         } catch (IOException e) {
184             throw new UncheckedIOException(e);
185         }
186     }
187 
188     /**
189      * Returns {@code true} if passed in artifact is originating from local repository. In other words, we want
190      * to process and store tracking information ONLY into local repository, not to any other place. This method
191      * filters out currently built artifacts, as events are fired for them as well, but their resolved artifact
192      * file would point to checked out source-tree, not the local repository.
193      * <p>
194      * Visible for testing.
195      */
196     static boolean isLocalRepositoryArtifactOrMissing(RepositorySystemSession session, Artifact artifact) {
197         return artifact.getFile() == null
198                 || artifact.getFile()
199                         .getPath()
200                         .startsWith(session.getLocalRepository().getBasedir().getPath());
201     }
202 
203     /**
204      * Unravels trace tree (going upwards from current node), looking for {@link CollectStepData} trace data.
205      * This method may return {@code null} if no collect step data found in passed trace data or it's parents.
206      * <p>
207      * Visible for testing.
208      */
209     static CollectStepData lookupCollectStepData(RequestTrace trace) {
210         CollectStepData collectStepTrace = null;
211         while (trace != null) {
212             if (trace.getData() instanceof CollectStepData) {
213                 collectStepTrace = (CollectStepData) trace.getData();
214                 break;
215             }
216             trace = trace.getParent();
217         }
218         return collectStepTrace;
219     }
220 
221     /**
222      * The event "artifact resolved" if fired WHENEVER an artifact is resolved, BUT it happens also when an artifact
223      * descriptor (model, the POM) is being built, and parent (and parent of parent...) is being asked for. Hence, this
224      * method "filters" out in WHICH artifact are we interested in, but it intentionally neglects extension as
225      * ArtifactDescriptorReader modifies extension to "pom" during collect. So all we have to rely on is GAV only.
226      */
227     static boolean isInScope(Artifact artifact, Artifact nodeArtifact) {
228         return Objects.equals(artifact.getGroupId(), nodeArtifact.getGroupId())
229                 && Objects.equals(artifact.getArtifactId(), nodeArtifact.getArtifactId())
230                 && Objects.equals(artifact.getVersion(), nodeArtifact.getVersion());
231     }
232 }