diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/collection/CollectStepData.java b/maven-resolver-api/src/main/java/org/eclipse/aether/collection/CollectStepData.java new file mode 100644 index 000000000..28036e9d7 --- /dev/null +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/collection/CollectStepData.java @@ -0,0 +1,50 @@ +package org.eclipse.aether.collection; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.util.List; + +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.graph.DependencyNode; + +/** + * A trace data object revealing collect step (while executing {@link CollectRequest}. + * + * @see org.eclipse.aether.RequestTrace + * @since 1.8.1 + */ +public interface CollectStepData +{ + /** + * Returns the context of collection. Never {@code null}. + */ + String getContext(); + + /** + * Returns the path of dependency nodes that led collector to current node returned by {@link #getNode()}. + * Never {@code null}. + */ + List getPath(); + + /** + * Returns the current node being collected. Never {@code null}. + */ + Dependency getNode(); +} diff --git a/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/AllResolverDemos.java b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/AllResolverDemos.java index 74bb3d48c..bb260aaff 100644 --- a/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/AllResolverDemos.java +++ b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/AllResolverDemos.java @@ -39,6 +39,7 @@ public static void main( String[] args ) GetDependencyHierarchy.main( args ); ResolveArtifact.main( args ); ResolveTransitiveDependencies.main( args ); + ReverseDependencyTree.main( args ); InstallArtifacts.main( args ); DeployArtifacts.main( args ); } diff --git a/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/ReverseDependencyTree.java b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/ReverseDependencyTree.java new file mode 100644 index 000000000..97b0b4af1 --- /dev/null +++ b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/ReverseDependencyTree.java @@ -0,0 +1,83 @@ +package org.apache.maven.resolver.examples; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.maven.resolver.examples.util.Booter; +import org.apache.maven.resolver.examples.util.ReverseTreeRepositoryListener; +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.collection.CollectRequest; +import org.eclipse.aether.resolution.ArtifactDescriptorRequest; +import org.eclipse.aether.resolution.ArtifactDescriptorResult; +import org.eclipse.aether.util.graph.manager.DependencyManagerUtils; +import org.eclipse.aether.util.graph.transformer.ConflictResolver; +import org.eclipse.aether.util.listener.ChainedRepositoryListener; + +/** + * Example of building reverse dependency tree using custom {@link ReverseTreeRepositoryListener}. + */ +public class ReverseDependencyTree +{ + + /** + * Main. + * @param args + * @throws Exception + */ + public static void main( String[] args ) + throws Exception + { + System.out.println( "------------------------------------------------------------" ); + System.out.println( ReverseDependencyTree.class.getSimpleName() ); + + RepositorySystem system = Booter.newRepositorySystem( Booter.selectFactory( args ) ); + + DefaultRepositorySystemSession session = Booter.newRepositorySystemSession( system ); + + // install the listener into session + session.setRepositoryListener( new ChainedRepositoryListener( session.getRepositoryListener(), + new ReverseTreeRepositoryListener() ) ); + + session.setConfigProperty( ConflictResolver.CONFIG_PROP_VERBOSE, true ); + session.setConfigProperty( DependencyManagerUtils.CONFIG_PROP_VERBOSE, true ); + + Artifact artifact = new DefaultArtifact( "org.apache.maven:maven-resolver-provider:3.6.1" ); + + ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest(); + descriptorRequest.setArtifact( artifact ); + descriptorRequest.setRepositories( Booter.newRepositories( system, session ) ); + ArtifactDescriptorResult descriptorResult = system.readArtifactDescriptor( session, descriptorRequest ); + + CollectRequest collectRequest = new CollectRequest(); + collectRequest.setRequestContext( "demo" ); + collectRequest.setRootArtifact( descriptorResult.getArtifact() ); + collectRequest.setDependencies( descriptorResult.getDependencies() ); + collectRequest.setManagedDependencies( descriptorResult.getManagedDependencies() ); + collectRequest.setRepositories( descriptorRequest.getRepositories() ); + + system.collectDependencies( session, collectRequest ); + + // in this demo we are not interested in collect result, + // as all the "demo work" is done by installed ReverseTreeRepositoryListener + } + +} diff --git a/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/util/ReverseTreeRepositoryListener.java b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/util/ReverseTreeRepositoryListener.java new file mode 100644 index 000000000..a13a768a8 --- /dev/null +++ b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/util/ReverseTreeRepositoryListener.java @@ -0,0 +1,115 @@ +package org.apache.maven.resolver.examples.util; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ListIterator; +import java.util.Objects; + +import org.eclipse.aether.AbstractRepositoryListener; +import org.eclipse.aether.RepositoryEvent; +import org.eclipse.aether.RequestTrace; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.collection.CollectStepData; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.graph.DependencyNode; + +import static java.util.Objects.requireNonNull; + +/** + * A demo class building reverse tree using {@link CollectStepData} trace data provided in {@link RepositoryEvent} + * events fired during collection. + */ +public class ReverseTreeRepositoryListener + extends AbstractRepositoryListener +{ + private static final String EOL = System.lineSeparator(); + + @Override + public void artifactResolved( RepositoryEvent event ) + { + requireNonNull( event, "event cannot be null" ); + + RequestTrace trace = event.getTrace(); + CollectStepData collectStepTrace = null; + while ( trace != null ) + { + if ( trace.getData() instanceof CollectStepData ) + { + collectStepTrace = (CollectStepData) trace.getData(); + break; + } + trace = trace.getParent(); + } + + if ( collectStepTrace == null ) + { + return; + } + + Artifact resolvedArtifact = event.getArtifact(); + Artifact nodeArtifact = collectStepTrace.getNode().getArtifact(); + + if ( isInScope( resolvedArtifact, nodeArtifact ) ) + { + Dependency node = collectStepTrace.getNode(); + String trackingData = node + " (" + collectStepTrace.getContext() + ")" + EOL; + String indent = ""; + ListIterator iter = collectStepTrace.getPath() + .listIterator( collectStepTrace.getPath().size() ); + while ( iter.hasPrevious() ) + { + DependencyNode curr = iter.previous(); + indent += " "; + trackingData += indent + curr + " (" + collectStepTrace.getContext() + ")" + EOL; + } + try + { + Path trackingDir = resolvedArtifact.getFile().getParentFile().toPath().resolve( ".tracking" ); + Files.createDirectories( trackingDir ); + Path trackingFile = trackingDir.resolve( collectStepTrace.getPath().get( 0 ) + .getArtifact().toString().replace( ":", "_" ) ); + Files.write( trackingFile, trackingData.getBytes( StandardCharsets.UTF_8 ) ); + System.out.println( trackingData ); + } + catch ( IOException e ) + { + throw new UncheckedIOException( e ); + } + } + } + + /** + * The event "artifact resolved" if fired WHENEVER an artifact is resolved, BUT it happens also when an artifact + * descriptor (model, the POM) is being built, and parent (and parent of parent...) is being asked for. Hence, this + * method "filters" out in WHICH artifact are we interested in, but it intentionally neglects extension as + * ArtifactDescriptorReader modifies extension to "pom" during collect. So all we have to rely on is GAV only. + */ + private boolean isInScope( Artifact artifact, Artifact nodeArtifact ) + { + return Objects.equals( artifact.getGroupId(), nodeArtifact.getGroupId() ) + && Objects.equals( artifact.getArtifactId(), nodeArtifact.getArtifactId() ) + && Objects.equals( artifact.getVersion(), nodeArtifact.getVersion() ); + } +} diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/CollectStepDataImpl.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/CollectStepDataImpl.java new file mode 100644 index 000000000..eb1df324c --- /dev/null +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/CollectStepDataImpl.java @@ -0,0 +1,67 @@ +package org.eclipse.aether.internal.impl.collect; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.util.List; + +import org.eclipse.aether.collection.CollectStepData; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.graph.DependencyNode; + +import static java.util.Objects.requireNonNull; + +/** + * Trace objects for dependency collection. + * + * @since 1.8.1 + */ +public final class CollectStepDataImpl implements CollectStepData +{ + private final String context; + + private final List path; + + private final Dependency node; + + public CollectStepDataImpl( final String context, final List path, final Dependency node ) + { + this.context = requireNonNull( context ); + this.path = requireNonNull( path ); + this.node = requireNonNull( node ); + } + + @Override + public String getContext() + { + return context; + } + + @Override + public List getPath() + { + return path; + } + + @Override + public Dependency getNode() + { + return node; + } +} diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate.java index 0a54f1b79..0897cc683 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate.java @@ -290,6 +290,33 @@ public final CollectResult collectDependencies( RepositorySystemSession session, return result; } + /** + * Creates child {@link RequestTrace} instance from passed in {@link RequestTrace} and parameters by creating + * {@link CollectStepDataImpl} instance out of passed in data. Caller must ensure that passed in parameters are + * NOT affected by threading (or that there is no multi threading involved). In other words, the passed in values + * should be immutable. + * + * @param trace The current trace instance. + * @param context The context from {@link CollectRequest#getRequestContext()}, never {@code null}. + * @param path List representing the path of dependency nodes, never {@code null}. Caller must ensure, that this + * list does not change during the lifetime of the requested {@link RequestTrace} instance. If it may + * change, simplest is to pass here a copy of used list. + * @param node Currently collected node, that collector came by following the passed in path. + * @return A child request trance instance, never {@code null}. + */ + protected RequestTrace collectStepTrace( RequestTrace trace, String context, List path, + Dependency node ) + { + return RequestTrace.newChild( + trace, + new CollectStepDataImpl( + context, + path, + node + ) + ); + } + @SuppressWarnings( "checkstyle:parameternumber" ) protected abstract void doCollectDependencies( RepositorySystemSession session, RequestTrace trace, DataPool pool, DefaultDependencyCollectionContext context, diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java index 8542dc7c1..c82eed941 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java @@ -126,7 +126,7 @@ protected void doCollectDependencies( RepositorySystemSession session, RequestTr } Args args = - new Args( session, trace, pool, context, versionContext, request, + new Args( session, pool, context, versionContext, request, useSkip ? DependencyResolutionSkipper.defaultSkipper() : DependencyResolutionSkipper.neverSkipper() ); @@ -150,7 +150,7 @@ protected void doCollectDependencies( RepositorySystemSession session, RequestTr while ( !args.dependencyProcessingQueue.isEmpty() ) { - processDependency( args, results, args.dependencyProcessingQueue.remove(), Collections.emptyList(), + processDependency( args, trace, results, args.dependencyProcessingQueue.remove(), Collections.emptyList(), false ); } @@ -158,15 +158,17 @@ protected void doCollectDependencies( RepositorySystemSession session, RequestTr } @SuppressWarnings( "checkstyle:parameternumber" ) - private void processDependency( Args args, Results results, DependencyProcessingContext context, - List relocations, boolean disableVersionManagement ) + private void processDependency( Args args, RequestTrace parent, Results results, + DependencyProcessingContext context, List relocations, + boolean disableVersionManagement ) { - if ( context.depSelector != null && !context.depSelector.selectDependency( context.dependency ) ) { return; } + RequestTrace trace = collectStepTrace( parent, args.request.getRequestContext(), context.parents, + context.dependency ); PremanagedDependency preManaged = PremanagedDependency.create( context.depManager, context.dependency, disableVersionManagement, args.premanagedState ); @@ -182,7 +184,7 @@ private void processDependency( Args args, Results results, DependencyProcessing VersionRangeResult rangeResult; try { - VersionRangeRequest rangeRequest = createVersionRangeRequest( args.request.getRequestContext(), args.trace, + VersionRangeRequest rangeRequest = createVersionRangeRequest( args.request.getRequestContext(), trace, context.repositories, dependency ); rangeResult = cachedResolveRangeResult( rangeRequest, args.pool, args.session ); @@ -203,7 +205,7 @@ private void processDependency( Args args, Results results, DependencyProcessing Dependency d = dependency.setArtifact( originalArtifact ); ArtifactDescriptorRequest descriptorRequest = createArtifactDescriptorRequest( - args.request.getRequestContext(), args.trace, context.repositories, d ); + args.request.getRequestContext(), trace, context.repositories, d ); final ArtifactDescriptorResult descriptorResult = noDescriptor @@ -236,8 +238,8 @@ private void processDependency( Args args, Results results, DependencyProcessing originalArtifact.getGroupId().equals( d.getArtifact().getGroupId() ) && originalArtifact.getArtifactId().equals( d.getArtifact().getArtifactId() ); - processDependency( args, results, context.withDependency( d ), descriptorResult.getRelocations(), - disableVersionManagementSubsequently ); + processDependency( args, parent, results, context.withDependency( d ), + descriptorResult.getRelocations(), disableVersionManagementSubsequently ); return; } else @@ -372,8 +374,6 @@ static class Args final boolean premanagedState; - final RequestTrace trace; - final DataPool pool; final Queue dependencyProcessingQueue = new ArrayDeque<>( 128 ); @@ -386,7 +386,7 @@ static class Args final DependencyResolutionSkipper skipper; - Args( RepositorySystemSession session, RequestTrace trace, DataPool pool, + Args( RepositorySystemSession session, DataPool pool, DefaultDependencyCollectionContext collectionContext, DefaultVersionFilterContext versionContext, CollectRequest request, DependencyResolutionSkipper skipper ) { @@ -394,7 +394,6 @@ static class Args this.request = request; this.ignoreRepos = session.isIgnoreArtifactDescriptorRepositories(); this.premanagedState = ConfigUtils.getBoolean( session, false, DependencyManagerUtils.CONFIG_PROP_VERBOSE ); - this.trace = trace; this.pool = pool; this.collectionContext = collectionContext; this.versionContext = versionContext; diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector.java index 4bfaf872b..268100f0e 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector.java @@ -102,9 +102,9 @@ protected void doCollectDependencies( RepositorySystemSession session, RequestTr NodeStack nodes = new NodeStack(); nodes.push( node ); - Args args = new Args( session, trace, pool, nodes, context, versionContext, request ); + Args args = new Args( session, pool, nodes, context, versionContext, request ); - process( args, results, dependencies, repositories, + process( args, trace, results, dependencies, repositories, session.getDependencySelector() != null ? session.getDependencySelector().deriveChildSelector( context ) : null, session.getDependencyManager() != null @@ -116,40 +116,41 @@ protected void doCollectDependencies( RepositorySystemSession session, RequestTr } @SuppressWarnings( "checkstyle:parameternumber" ) - private void process( final Args args, Results results, List dependencies, + private void process( final Args args, RequestTrace trace, Results results, List dependencies, List repositories, DependencySelector depSelector, DependencyManager depManager, DependencyTraverser depTraverser, VersionFilter verFilter ) { for ( Dependency dependency : dependencies ) { - processDependency( args, results, repositories, depSelector, depManager, depTraverser, verFilter, + processDependency( args, trace, results, repositories, depSelector, depManager, depTraverser, verFilter, dependency ); } } @SuppressWarnings( "checkstyle:parameternumber" ) - private void processDependency( Args args, Results results, List repositories, + private void processDependency( Args args, RequestTrace trace, Results results, List repositories, DependencySelector depSelector, DependencyManager depManager, DependencyTraverser depTraverser, VersionFilter verFilter, Dependency dependency ) { List relocations = Collections.emptyList(); - processDependency( args, results, repositories, depSelector, depManager, depTraverser, verFilter, dependency, - relocations, false ); + processDependency( args, trace, results, repositories, depSelector, depManager, depTraverser, verFilter, + dependency, relocations, false ); } @SuppressWarnings( "checkstyle:parameternumber" ) - private void processDependency( Args args, Results results, List repositories, - DependencySelector depSelector, DependencyManager depManager, - DependencyTraverser depTraverser, VersionFilter verFilter, Dependency dependency, - List relocations, boolean disableVersionManagement ) + private void processDependency( Args args, RequestTrace parent, Results results, + List repositories, DependencySelector depSelector, + DependencyManager depManager, DependencyTraverser depTraverser, + VersionFilter verFilter, Dependency dependency, List relocations, + boolean disableVersionManagement ) { - if ( depSelector != null && !depSelector.selectDependency( dependency ) ) { return; } + RequestTrace trace = collectStepTrace( parent, args.request.getRequestContext(), args.nodes.nodes, dependency ); PremanagedDependency preManaged = PremanagedDependency.create( depManager, dependency, disableVersionManagement, args.premanagedState ); dependency = preManaged.getManagedDependency(); @@ -162,7 +163,7 @@ private void processDependency( Args args, Results results, List repositories, + private void doRecurse( Args args, RequestTrace trace, Results results, List repositories, DependencySelector depSelector, DependencyManager depManager, DependencyTraverser depTraverser, VersionFilter verFilter, Dependency d, ArtifactDescriptorResult descriptorResult, DefaultDependencyNode child ) @@ -280,7 +281,7 @@ private void doRecurse( Args args, Results results, List repos args.nodes.push( child ); - process( args, results, descriptorResult.getDependencies(), childRepos, childSelector, childManager, + process( args, trace, results, descriptorResult.getDependencies(), childRepos, childSelector, childManager, childTraverser, childFilter ); args.nodes.pop(); @@ -340,8 +341,6 @@ static class Args final boolean premanagedState; - final RequestTrace trace; - final DataPool pool; final NodeStack nodes; @@ -352,7 +351,7 @@ static class Args final CollectRequest request; - Args( RepositorySystemSession session, RequestTrace trace, DataPool pool, NodeStack nodes, + Args( RepositorySystemSession session, DataPool pool, NodeStack nodes, DefaultDependencyCollectionContext collectionContext, DefaultVersionFilterContext versionContext, CollectRequest request ) { @@ -360,7 +359,6 @@ static class Args this.request = request; this.ignoreRepos = session.isIgnoreArtifactDescriptorRepositories(); this.premanagedState = ConfigUtils.getBoolean( session, false, DependencyManagerUtils.CONFIG_PROP_VERBOSE ); - this.trace = trace; this.pool = pool; this.nodes = nodes; this.collectionContext = collectionContext;