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