diff --git a/src/main/java/org/apache/maven/plugins/dependency/ListClassesMojo.java b/src/main/java/org/apache/maven/plugins/dependency/ListClassesMojo.java new file mode 100644 index 000000000..fa33031a8 --- /dev/null +++ b/src/main/java/org/apache/maven/plugins/dependency/ListClassesMojo.java @@ -0,0 +1,326 @@ +package org.apache.maven.plugins.dependency; + +/* + * 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.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.maven.artifact.handler.ArtifactHandler; +import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy; +import org.apache.maven.artifact.repository.MavenArtifactRepository; +import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.DefaultProjectBuildingRequest; +import org.apache.maven.project.ProjectBuildingRequest; +import org.apache.maven.repository.RepositorySystem; +import org.apache.maven.settings.Settings; +import org.apache.maven.shared.transfer.artifact.ArtifactCoordinate; +import org.apache.maven.shared.transfer.artifact.DefaultArtifactCoordinate; +import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver; +import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException; +import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult; +import org.apache.maven.shared.transfer.dependencies.DefaultDependableCoordinate; +import org.apache.maven.shared.transfer.dependencies.DependableCoordinate; +import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver; +import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolverException; + + +/** + * Retrieves and lists all classes contained in the specified artifact from the specified remote repositories. + */ +@Mojo( name = "list-classes", requiresProject = false, threadSafe = true ) +public class ListClassesMojo + extends AbstractMojo +{ + private static final Pattern ALT_REPO_SYNTAX_PATTERN = Pattern.compile( "(.+)::(.*)::(.+)" ); + + @Parameter( defaultValue = "${session}", required = true, readonly = true ) + private MavenSession session; + + @Component + private ArtifactResolver artifactResolver; + + @Component + private DependencyResolver dependencyResolver; + + @Component + private ArtifactHandlerManager artifactHandlerManager; + + /** + * Map that contains the layouts. + */ + @Component( role = ArtifactRepositoryLayout.class ) + private Map repositoryLayouts; + + /** + * The repository system. + */ + @Component + private RepositorySystem repositorySystem; + + private DefaultDependableCoordinate coordinate = new DefaultDependableCoordinate(); + + /** + * The group ID of the artifact to download. Ignored if {@link #artifact} is used. + */ + @Parameter( property = "groupId" ) + private String groupId; + + /** + * The artifact ID of the artifact to download. Ignored if {@link #artifact} is used. + */ + @Parameter( property = "artifactId" ) + private String artifactId; + + /** + * The version of the artifact to download. Ignored if {@link #artifact} is used. + */ + @Parameter( property = "version" ) + private String version; + + /** + * The classifier of the artifact to download. Ignored if {@link #artifact} is used. + * + * @since 2.3 + */ + @Parameter( property = "classifier" ) + private String classifier; + + /** + * The packaging of the artifact to download. Ignored if {@link #artifact} is used. + */ + @Parameter( property = "packaging", defaultValue = "jar" ) + private String packaging = "jar"; + + /** + * Repositories in the format id::[layout]::url or just URLs, separated by comma. That is, + * central::default::https://repo.maven.apache.org/maven2,myrepo::::https://repo.acme.com,https://repo.acme2.com + */ + @Parameter( property = "remoteRepositories" ) + private String remoteRepositories; + + /** + * A string of the form groupId:artifactId:version[:packaging[:classifier]]. + */ + @Parameter( property = "artifact" ) + private String artifact; + + @Parameter( defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true ) + private List pomRemoteRepositories; + + /** + * Download transitively, retrieving the specified artifact and all of its dependencies. + */ + @Parameter( property = "transitive", defaultValue = "false" ) + private boolean transitive = false; + + /** + * Skip plugin execution completely. + */ + @Parameter( property = "mdep.skip", defaultValue = "false" ) + private boolean skip; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException + { + ProjectBuildingRequest buildingRequest = makeBuildingRequest(); + + try + { + if ( transitive ) + { + Iterable artifacts = dependencyResolver + .resolveDependencies( buildingRequest, coordinate, null ); + + for ( ArtifactResult result : artifacts ) + { + printClassesFromArtifactResult( result ); + } + } + else + { + ArtifactResult result = artifactResolver + .resolveArtifact( buildingRequest, toArtifactCoordinate( coordinate ) ); + + printClassesFromArtifactResult( result ); + } + } + catch ( ArtifactResolverException | DependencyResolverException | IOException e ) + { + throw new MojoExecutionException( "Couldn't download artifact: " + e.getMessage(), e ); + } + } + + private void printClassesFromArtifactResult( ArtifactResult result ) + throws IOException + { + // open jar file in try-with-resources statement to guarantee the file closes after use regardless of errors + try ( JarFile jarFile = new JarFile( result.getArtifact().getFile() ) ) + { + Enumeration entries = jarFile.entries(); + + while ( entries.hasMoreElements() ) + { + JarEntry entry = (JarEntry) entries.nextElement(); + String entryName = entry.getName(); + + // filter out files that do not end in .class + if ( !entryName.endsWith( ".class" ) ) + { + continue; + } + + // remove .class from the end and change format to use periods instead of forward slashes + String className = entryName.substring( 0, entryName.length() - 6 ).replace( '/', '.' ); + getLog().info( className ); + } + } + } + + private ProjectBuildingRequest makeBuildingRequest() + throws MojoExecutionException, MojoFailureException + { + if ( artifact == null ) + { + throw new MojoFailureException( "You must specify an artifact, " + + "e.g. -Dartifact=org.apache.maven.plugins:maven-downloader-plugin:1.0" ); + } + if ( artifact != null ) + { + String[] tokens = artifact.split( ":" ); + if ( tokens.length < 3 || tokens.length > 5 ) + { + throw new MojoFailureException( "Invalid artifact, you must specify " + + "groupId:artifactId:version[:packaging[:classifier]] " + artifact ); + } + coordinate.setGroupId( tokens[0] ); + coordinate.setArtifactId( tokens[1] ); + coordinate.setVersion( tokens[2] ); + if ( tokens.length >= 4 ) + { + coordinate.setType( tokens[3] ); + } + if ( tokens.length == 5 ) + { + coordinate.setClassifier( tokens[4] ); + } + } + + ArtifactRepositoryPolicy always = + new ArtifactRepositoryPolicy( true, ArtifactRepositoryPolicy.UPDATE_POLICY_ALWAYS, + ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN ); + + List repoList = new ArrayList<>(); + + if ( pomRemoteRepositories != null ) + { + repoList.addAll( pomRemoteRepositories ); + } + + if ( remoteRepositories != null ) + { + // Use the same format as in the deploy plugin id::layout::url + String[] repos = remoteRepositories.split( "," ); + for ( String repo : repos ) + { + repoList.add( parseRepository( repo, always ) ); + } + } + + ProjectBuildingRequest buildingRequest = + new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() ); + + Settings settings = session.getSettings(); + repositorySystem.injectMirror( repoList, settings.getMirrors() ); + repositorySystem.injectProxy( repoList, settings.getProxies() ); + repositorySystem.injectAuthentication( repoList, settings.getServers() ); + + buildingRequest.setRemoteRepositories( repoList ); + + return buildingRequest; + } + + protected ArtifactCoordinate toArtifactCoordinate( DependableCoordinate dependableCoordinate ) + { + ArtifactHandler artifactHandler = artifactHandlerManager.getArtifactHandler( dependableCoordinate.getType() ); + DefaultArtifactCoordinate artifactCoordinate = new DefaultArtifactCoordinate(); + artifactCoordinate.setGroupId( dependableCoordinate.getGroupId() ); + artifactCoordinate.setArtifactId( dependableCoordinate.getArtifactId() ); + artifactCoordinate.setVersion( dependableCoordinate.getVersion() ); + artifactCoordinate.setClassifier( dependableCoordinate.getClassifier() ); + artifactCoordinate.setExtension( artifactHandler.getExtension() ); + return artifactCoordinate; + } + + protected ArtifactRepository parseRepository( String repo, ArtifactRepositoryPolicy policy ) + throws MojoFailureException + { + // if it's a simple url + String id = "temp"; + ArtifactRepositoryLayout layout = getLayout( "default" ); + + // if it's an extended repo URL of the form id::layout::url + if ( repo.contains( "::" ) ) + { + Matcher matcher = ALT_REPO_SYNTAX_PATTERN.matcher( repo ); + if ( !matcher.matches() ) + { + throw new MojoFailureException( repo, "Invalid syntax for repository: " + repo, + "Invalid syntax for repository. Use \"id::layout::url\" or \"URL\"." ); + } + + id = matcher.group( 1 ).trim(); + if ( !( matcher.group( 2 ) == null || matcher.group( 2 ).trim().isEmpty() ) ) + { + layout = getLayout( matcher.group( 2 ).trim() ); + } + repo = matcher.group( 3 ).trim(); + } + return new MavenArtifactRepository( id, repo, layout, policy, policy ); + } + + private ArtifactRepositoryLayout getLayout( String id ) + throws MojoFailureException + { + ArtifactRepositoryLayout layout = repositoryLayouts.get( id ); + + if ( layout == null ) + { + throw new MojoFailureException( id, "Invalid repository layout", "Invalid repository layout: " + id ); + } + + return layout; + } +} diff --git a/src/test/java/org/apache/maven/plugins/dependency/TestGetClassesMojo.java b/src/test/java/org/apache/maven/plugins/dependency/TestGetClassesMojo.java new file mode 100644 index 000000000..6139ed747 --- /dev/null +++ b/src/test/java/org/apache/maven/plugins/dependency/TestGetClassesMojo.java @@ -0,0 +1,87 @@ +package org.apache.maven.plugins.dependency; + +/* + * 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.execution.MavenSession; +import org.apache.maven.plugin.LegacySupport; +import org.apache.maven.plugin.testing.stubs.MavenProjectStub; +import org.apache.maven.settings.Server; +import org.apache.maven.settings.Settings; +import org.junit.Assert; +import org.sonatype.aether.impl.internal.SimpleLocalRepositoryManager; +import org.sonatype.aether.util.DefaultRepositorySystemSession; + +import java.io.File; + +public class TestGetClassesMojo + extends AbstractDependencyMojoTestCase +{ + private ListClassesMojo mojo; + + protected void setUp() + throws Exception + { + // required for mojo lookups to work + super.setUp( "markers", false ); + + File testPom = new File( getBasedir(), "target/test-classes/unit/get-test/plugin-config.xml" ); + assertTrue(testPom.exists()); + mojo = (ListClassesMojo) lookupMojo( "list-classes", testPom ); + + assertNotNull( mojo ); + + LegacySupport legacySupport = lookup( LegacySupport.class ); + MavenSession session = newMavenSession( new MavenProjectStub() ); + Settings settings = session.getSettings(); + Server server = new Server(); + server.setId( "myserver" ); + server.setUsername( "foo" ); + server.setPassword( "bar" ); + settings.addServer( server ); + legacySupport.setSession( session ); + DefaultRepositorySystemSession repoSession = + (DefaultRepositorySystemSession) legacySupport.getRepositorySession(); + repoSession.setLocalRepositoryManager( new SimpleLocalRepositoryManager( testDir.getAbsolutePath() ) ); + + setVariableValueToObject( mojo, "session", legacySupport.getSession() ); + } + + public void testGetClassesNotTransitive() + throws Exception + { + setVariableValueToObject( mojo, "remoteRepositories", "central::default::https://repo.maven.apache.org/maven2," + + "central::::https://repo.maven.apache.org/maven2," + "https://repo.maven.apache.org/maven2" ); + setVariableValueToObject( mojo, "artifact", "org.apache.commons:commons-lang3:3.6" ); + setVariableValueToObject( mojo, "transitive", Boolean.FALSE ); + + mojo.execute(); + } + + public void testGetClassesTransitive() + throws Exception + { + setVariableValueToObject( mojo, "remoteRepositories", "central::default::https://repo.maven.apache.org/maven2," + + "central::::https://repo.maven.apache.org/maven2," + "https://repo.maven.apache.org/maven2" ); + setVariableValueToObject( mojo, "artifact", "org.apache.commons:commons-lang3:3.6" ); + setVariableValueToObject( mojo, "transitive", Boolean.TRUE ); + + mojo.execute(); + } +}