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