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.plugins.dependency;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Enumeration;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.jar.JarEntry;
27  import java.util.jar.JarFile;
28  import java.util.regex.Matcher;
29  import java.util.regex.Pattern;
30  import org.apache.maven.artifact.handler.ArtifactHandler;
31  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
32  import org.apache.maven.artifact.repository.ArtifactRepository;
33  import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
34  import org.apache.maven.artifact.repository.MavenArtifactRepository;
35  import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
36  import org.apache.maven.execution.MavenSession;
37  import org.apache.maven.plugin.AbstractMojo;
38  import org.apache.maven.plugin.MojoExecutionException;
39  import org.apache.maven.plugin.MojoFailureException;
40  import org.apache.maven.plugins.annotations.Component;
41  import org.apache.maven.plugins.annotations.Mojo;
42  import org.apache.maven.plugins.annotations.Parameter;
43  import org.apache.maven.project.DefaultProjectBuildingRequest;
44  import org.apache.maven.project.ProjectBuildingRequest;
45  import org.apache.maven.repository.RepositorySystem;
46  import org.apache.maven.settings.Settings;
47  import org.apache.maven.shared.transfer.artifact.ArtifactCoordinate;
48  import org.apache.maven.shared.transfer.artifact.DefaultArtifactCoordinate;
49  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver;
50  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException;
51  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult;
52  import org.apache.maven.shared.transfer.dependencies.DefaultDependableCoordinate;
53  import org.apache.maven.shared.transfer.dependencies.DependableCoordinate;
54  import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
55  import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolverException;
56  
57  /**
58   * Retrieves and lists all classes contained in the specified artifact from the specified remote repositories.
59   */
60  @Mojo(name = "list-classes", requiresProject = false, threadSafe = true)
61  public class ListClassesMojo extends AbstractMojo {
62      private static final Pattern ALT_REPO_SYNTAX_PATTERN = Pattern.compile("(.+)::(.*)::(.+)");
63  
64      @Parameter(defaultValue = "${session}", required = true, readonly = true)
65      private MavenSession session;
66  
67      @Component
68      private ArtifactResolver artifactResolver;
69  
70      @Component
71      private DependencyResolver dependencyResolver;
72  
73      @Component
74      private ArtifactHandlerManager artifactHandlerManager;
75  
76      /**
77       * Map that contains the layouts.
78       */
79      @Component(role = ArtifactRepositoryLayout.class)
80      private Map<String, ArtifactRepositoryLayout> repositoryLayouts;
81  
82      /**
83       * The repository system.
84       */
85      @Component
86      private RepositorySystem repositorySystem;
87  
88      private DefaultDependableCoordinate coordinate = new DefaultDependableCoordinate();
89  
90      /**
91       * The group ID of the artifact to download. Ignored if {@link #artifact} is used.
92       */
93      @Parameter(property = "groupId")
94      private String groupId;
95  
96      /**
97       * The artifact ID of the artifact to download. Ignored if {@link #artifact} is used.
98       */
99      @Parameter(property = "artifactId")
100     private String artifactId;
101 
102     /**
103      * The version of the artifact to download. Ignored if {@link #artifact} is used.
104      */
105     @Parameter(property = "version")
106     private String version;
107 
108     /**
109      * The classifier of the artifact to download. Ignored if {@link #artifact} is used.
110      *
111      * @since 2.3
112      */
113     @Parameter(property = "classifier")
114     private String classifier;
115 
116     /**
117      * The packaging of the artifact to download. Ignored if {@link #artifact} is used.
118      */
119     @Parameter(property = "packaging", defaultValue = "jar")
120     private String packaging = "jar";
121 
122     /**
123      * Repositories in the format id::[layout]::url or just URLs, separated by comma. That is,
124      * central::default::https://repo.maven.apache.org/maven2,myrepo::::https://repo.acme.com,https://repo.acme2.com
125      */
126     @Parameter(property = "remoteRepositories")
127     private String remoteRepositories;
128 
129     /**
130      * A string of the form groupId:artifactId:version[:packaging[:classifier]].
131      */
132     @Parameter(property = "artifact")
133     private String artifact;
134 
135     @Parameter(defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true)
136     private List<ArtifactRepository> pomRemoteRepositories;
137 
138     /**
139      * Download transitively, retrieving the specified artifact and all of its dependencies.
140      */
141     @Parameter(property = "transitive", defaultValue = "false")
142     private boolean transitive = false;
143 
144     /**
145      * Skip plugin execution completely.
146      */
147     @Parameter(property = "mdep.skip", defaultValue = "false")
148     private boolean skip;
149 
150     @Override
151     public void execute() throws MojoExecutionException, MojoFailureException {
152         ProjectBuildingRequest buildingRequest = makeBuildingRequest();
153 
154         try {
155             if (transitive) {
156                 Iterable<ArtifactResult> artifacts =
157                         dependencyResolver.resolveDependencies(buildingRequest, coordinate, null);
158 
159                 for (ArtifactResult result : artifacts) {
160                     printClassesFromArtifactResult(result);
161                 }
162             } else {
163                 ArtifactResult result =
164                         artifactResolver.resolveArtifact(buildingRequest, toArtifactCoordinate(coordinate));
165 
166                 printClassesFromArtifactResult(result);
167             }
168         } catch (ArtifactResolverException | DependencyResolverException | IOException e) {
169             throw new MojoExecutionException("Couldn't download artifact: " + e.getMessage(), e);
170         }
171     }
172 
173     private void printClassesFromArtifactResult(ArtifactResult result) throws IOException {
174         // open jar file in try-with-resources statement to guarantee the file closes after use regardless of errors
175         try (JarFile jarFile = new JarFile(result.getArtifact().getFile())) {
176             Enumeration<JarEntry> entries = jarFile.entries();
177 
178             while (entries.hasMoreElements()) {
179                 JarEntry entry = entries.nextElement();
180                 String entryName = entry.getName();
181 
182                 // filter out files that do not end in .class
183                 if (!entryName.endsWith(".class")) {
184                     continue;
185                 }
186 
187                 // remove .class from the end and change format to use periods instead of forward slashes
188                 String className =
189                         entryName.substring(0, entryName.length() - 6).replace('/', '.');
190                 getLog().info(className);
191             }
192         }
193     }
194 
195     private ProjectBuildingRequest makeBuildingRequest() throws MojoExecutionException, MojoFailureException {
196         if (artifact == null) {
197             throw new MojoFailureException("You must specify an artifact, "
198                     + "e.g. -Dartifact=org.apache.maven.plugins:maven-downloader-plugin:1.0");
199         }
200 
201         String[] tokens = artifact.split(":");
202         if (tokens.length < 3 || tokens.length > 5) {
203             throw new MojoFailureException("Invalid artifact, you must specify "
204                     + "groupId:artifactId:version[:packaging[:classifier]] " + artifact);
205         }
206         coordinate.setGroupId(tokens[0]);
207         coordinate.setArtifactId(tokens[1]);
208         coordinate.setVersion(tokens[2]);
209         if (tokens.length >= 4) {
210             coordinate.setType(tokens[3]);
211         }
212         if (tokens.length == 5) {
213             coordinate.setClassifier(tokens[4]);
214         }
215 
216         ArtifactRepositoryPolicy always = new ArtifactRepositoryPolicy(
217                 true, ArtifactRepositoryPolicy.UPDATE_POLICY_ALWAYS, ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN);
218 
219         List<ArtifactRepository> repoList = new ArrayList<>();
220 
221         if (pomRemoteRepositories != null) {
222             repoList.addAll(pomRemoteRepositories);
223         }
224 
225         if (remoteRepositories != null) {
226             // Use the same format as in the deploy plugin id::layout::url
227             String[] repos = remoteRepositories.split(",");
228             for (String repo : repos) {
229                 repoList.add(parseRepository(repo, always));
230             }
231         }
232 
233         ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
234 
235         Settings settings = session.getSettings();
236         repositorySystem.injectMirror(repoList, settings.getMirrors());
237         repositorySystem.injectProxy(repoList, settings.getProxies());
238         repositorySystem.injectAuthentication(repoList, settings.getServers());
239 
240         buildingRequest.setRemoteRepositories(repoList);
241 
242         return buildingRequest;
243     }
244 
245     private ArtifactCoordinate toArtifactCoordinate(DependableCoordinate dependableCoordinate) {
246         ArtifactHandler artifactHandler = artifactHandlerManager.getArtifactHandler(dependableCoordinate.getType());
247         DefaultArtifactCoordinate artifactCoordinate = new DefaultArtifactCoordinate();
248         artifactCoordinate.setGroupId(dependableCoordinate.getGroupId());
249         artifactCoordinate.setArtifactId(dependableCoordinate.getArtifactId());
250         artifactCoordinate.setVersion(dependableCoordinate.getVersion());
251         artifactCoordinate.setClassifier(dependableCoordinate.getClassifier());
252         artifactCoordinate.setExtension(artifactHandler.getExtension());
253         return artifactCoordinate;
254     }
255 
256     protected ArtifactRepository parseRepository(String repo, ArtifactRepositoryPolicy policy)
257             throws MojoFailureException {
258         // if it's a simple url
259         String id = "temp";
260         ArtifactRepositoryLayout layout = getLayout("default");
261 
262         // if it's an extended repo URL of the form id::layout::url
263         if (repo.contains("::")) {
264             Matcher matcher = ALT_REPO_SYNTAX_PATTERN.matcher(repo);
265             if (!matcher.matches()) {
266                 throw new MojoFailureException(
267                         repo,
268                         "Invalid syntax for repository: " + repo,
269                         "Invalid syntax for repository. Use \"id::layout::url\" or \"URL\".");
270             }
271 
272             id = matcher.group(1).trim();
273             if (!(matcher.group(2) == null || matcher.group(2).trim().isEmpty())) {
274                 layout = getLayout(matcher.group(2).trim());
275             }
276             repo = matcher.group(3).trim();
277         }
278         return new MavenArtifactRepository(id, repo, layout, policy, policy);
279     }
280 
281     private ArtifactRepositoryLayout getLayout(String id) throws MojoFailureException {
282         ArtifactRepositoryLayout layout = repositoryLayouts.get(id);
283 
284         if (layout == null) {
285             throw new MojoFailureException(id, "Invalid repository layout", "Invalid repository layout: " + id);
286         }
287 
288         return layout;
289     }
290 }