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.IOException;
22  import java.io.UncheckedIOException;
23  import java.nio.charset.StandardCharsets;
24  import java.nio.file.Files;
25  import java.nio.file.Path;
26  import java.util.ArrayList;
27  import java.util.ListIterator;
28  import java.util.Objects;
29  
30  import org.eclipse.aether.AbstractRepositoryListener;
31  import org.eclipse.aether.RepositoryEvent;
32  import org.eclipse.aether.RepositorySystemSession;
33  import org.eclipse.aether.RequestTrace;
34  import org.eclipse.aether.artifact.Artifact;
35  import org.eclipse.aether.collection.CollectStepData;
36  import org.eclipse.aether.graph.Dependency;
37  import org.eclipse.aether.graph.DependencyNode;
38  
39  import static java.util.Objects.requireNonNull;
40  
41  /**
42   * A class building reverse tree using {@link CollectStepData} trace data provided in {@link RepositoryEvent}
43   * events fired during collection.
44   *
45   * @since 3.9.0
46   */
47  class ReverseTreeRepositoryListener extends AbstractRepositoryListener {
48      @Override
49      public void artifactResolved(RepositoryEvent event) {
50          requireNonNull(event, "event cannot be null");
51  
52          if (!isLocalRepositoryArtifact(event.getSession(), event.getArtifact())) {
53              return;
54          }
55  
56          CollectStepData collectStepTrace = lookupCollectStepData(event.getTrace());
57          if (collectStepTrace == null) {
58              return;
59          }
60  
61          Artifact resolvedArtifact = event.getArtifact();
62          Artifact nodeArtifact = collectStepTrace.getNode().getArtifact();
63  
64          if (isInScope(resolvedArtifact, nodeArtifact)) {
65              Dependency node = collectStepTrace.getNode();
66              ArrayList<String> trackingData = new ArrayList<>();
67              trackingData.add(node + " (" + collectStepTrace.getContext() + ")");
68              String indent = "";
69              ListIterator<DependencyNode> iter = collectStepTrace
70                      .getPath()
71                      .listIterator(collectStepTrace.getPath().size());
72              while (iter.hasPrevious()) {
73                  DependencyNode curr = iter.previous();
74                  indent += "  ";
75                  trackingData.add(indent + curr + " (" + collectStepTrace.getContext() + ")");
76              }
77              try {
78                  Path trackingDir =
79                          resolvedArtifact.getFile().getParentFile().toPath().resolve(".tracking");
80                  Files.createDirectories(trackingDir);
81                  Path trackingFile = trackingDir.resolve(collectStepTrace
82                          .getPath()
83                          .get(0)
84                          .getArtifact()
85                          .toString()
86                          .replace(":", "_"));
87                  Files.write(trackingFile, trackingData, StandardCharsets.UTF_8);
88              } catch (IOException e) {
89                  throw new UncheckedIOException(e);
90              }
91          }
92      }
93  
94      /**
95       * Returns {@code true} if passed in artifact is originating from local repository. In other words, we want
96       * to process and store tracking information ONLY into local repository, not to any other place. This method
97       * filters out currently built artifacts, as events are fired for them as well, but their resolved artifact
98       * file would point to checked out source-tree, not the local repository.
99       * <p>
100      * Visible for testing.
101      */
102     static boolean isLocalRepositoryArtifact(RepositorySystemSession session, Artifact artifact) {
103         return artifact.getFile()
104                 .getPath()
105                 .startsWith(session.getLocalRepository().getBasedir().getPath());
106     }
107 
108     /**
109      * Unravels trace tree (going upwards from current node), looking for {@link CollectStepData} trace data.
110      * This method may return {@code null} if no collect step data found in passed trace data or it's parents.
111      * <p>
112      * Visible for testing.
113      */
114     static CollectStepData lookupCollectStepData(RequestTrace trace) {
115         CollectStepData collectStepTrace = null;
116         while (trace != null) {
117             if (trace.getData() instanceof CollectStepData) {
118                 collectStepTrace = (CollectStepData) trace.getData();
119                 break;
120             }
121             trace = trace.getParent();
122         }
123         return collectStepTrace;
124     }
125 
126     /**
127      * The event "artifact resolved" if fired WHENEVER an artifact is resolved, BUT it happens also when an artifact
128      * descriptor (model, the POM) is being built, and parent (and parent of parent...) is being asked for. Hence, this
129      * method "filters" out in WHICH artifact are we interested in, but it intentionally neglects extension as
130      * ArtifactDescriptorReader modifies extension to "pom" during collect. So all we have to rely on is GAV only.
131      */
132     static boolean isInScope(Artifact artifact, Artifact nodeArtifact) {
133         return Objects.equals(artifact.getGroupId(), nodeArtifact.getGroupId())
134                 && Objects.equals(artifact.getArtifactId(), nodeArtifact.getArtifactId())
135                 && Objects.equals(artifact.getVersion(), nodeArtifact.getVersion());
136     }
137 }