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.fromConfiguration;
20  
21  import java.io.File;
22  import java.util.Collections;
23  import java.util.List;
24  import java.util.Objects;
25  
26  import org.apache.commons.lang3.StringUtils;
27  import org.apache.maven.RepositoryUtils;
28  import org.apache.maven.artifact.Artifact;
29  import org.apache.maven.artifact.handler.ArtifactHandler;
30  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
31  import org.apache.maven.execution.MavenSession;
32  import org.apache.maven.model.Dependency;
33  import org.apache.maven.plugin.MojoExecutionException;
34  import org.apache.maven.plugin.MojoFailureException;
35  import org.apache.maven.plugins.annotations.Parameter;
36  import org.apache.maven.plugins.dependency.AbstractDependencyMojo;
37  import org.apache.maven.plugins.dependency.utils.DependencyUtil;
38  import org.apache.maven.plugins.dependency.utils.filters.ArtifactItemFilter;
39  import org.apache.maven.project.MavenProject;
40  import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException;
41  import org.eclipse.aether.DefaultRepositoryCache;
42  import org.eclipse.aether.DefaultRepositorySystemSession;
43  import org.eclipse.aether.RepositorySystem;
44  import org.eclipse.aether.RepositorySystemSession;
45  import org.eclipse.aether.artifact.DefaultArtifact;
46  import org.eclipse.aether.repository.LocalRepository;
47  import org.eclipse.aether.repository.LocalRepositoryManager;
48  import org.eclipse.aether.resolution.ArtifactRequest;
49  import org.eclipse.aether.resolution.ArtifactResolutionException;
50  import org.eclipse.aether.resolution.ArtifactResult;
51  import org.sonatype.plexus.build.incremental.BuildContext;
52  
53  /**
54   * Abstract parent class used by mojos that get Artifact information from the plugin configuration as an ArrayList of
55   * ArtifactItems.
56   *
57   * @author <a href="mailto:brianf@apache.org">Brian Fox</a>
58   * @see ArtifactItem
59   */
60  public abstract class AbstractFromConfigurationMojo extends AbstractDependencyMojo {
61      /**
62       * Default output location used for mojo, unless overridden in ArtifactItem.
63       *
64       * @since 1.0
65       */
66      @Parameter(property = "outputDirectory", defaultValue = "${project.build.directory}/dependency")
67      private File outputDirectory;
68  
69      /**
70       * Overwrite release artifacts.
71       *
72       * @since 1.0
73       */
74      @Parameter(property = "mdep.overWriteReleases", defaultValue = "false")
75      private boolean overWriteReleases;
76  
77      /**
78       * Overwrite snapshot artifacts.
79       *
80       * @since 1.0
81       */
82      @Parameter(property = "mdep.overWriteSnapshots", defaultValue = "false")
83      private boolean overWriteSnapshots;
84  
85      /**
86       * Overwrite if newer.
87       *
88       * @since 2.0
89       * @deprecated use 'overWriteIfNewer' or 'mdep.overWriteIfNewer' as this does nothing now
90       */
91      @Deprecated
92      @Parameter(property = "mdep.overIfNewer", defaultValue = "true")
93      private boolean overIfNewer;
94  
95      /**
96       * Overwrite if newer.
97       *
98       * @since 3.7.0
99       */
100     @Parameter(property = "mdep.overWriteIfNewer", defaultValue = "true")
101     private boolean overWriteIfNewer;
102 
103     /**
104      * Collection of ArtifactItems to work on. (ArtifactItem contains groupId, artifactId, version, type, classifier,
105      * outputDirectory, destFileName, overWrite and encoding.) See <a href="./usage.html">Usage</a> for details.
106      *
107      * @since 1.0
108      */
109     @Parameter
110     private List<ArtifactItem> artifactItems;
111 
112     /**
113      * Path to override default local repository during plugin's execution. To remove all downloaded artifacts as part
114      * of the build, set this value to a location under your project's target directory.
115      *
116      * @since 2.2
117      */
118     @Parameter
119     private File localRepositoryDirectory;
120 
121     private final ArtifactHandlerManager artifactHandlerManager;
122 
123     private final RepositorySystem repositorySystem;
124 
125     protected AbstractFromConfigurationMojo(
126             MavenSession session,
127             BuildContext buildContext,
128             MavenProject project,
129             ArtifactHandlerManager artifactHandlerManager,
130             RepositorySystem repositorySystem) {
131         super(session, buildContext, project);
132         this.artifactHandlerManager = artifactHandlerManager;
133         this.repositorySystem = repositorySystem;
134     }
135 
136     abstract ArtifactItemFilter getMarkedArtifactFilter(ArtifactItem item);
137 
138     /**
139      * ArtifactItems is filled by either field injection or by setArtifact().
140      *
141      * @throws MojoFailureException in case of an error
142      */
143     protected void verifyRequirements() throws MojoFailureException {
144         if (artifactItems == null || artifactItems.isEmpty()) {
145             throw new MojoFailureException("Either artifact or artifactItems is required ");
146         }
147     }
148 
149     /**
150      * Preprocesses the list of ArtifactItems. This method defaults the outputDirectory if not set and creates the
151      * output Directory if it doesn't exist.
152      *
153      * @param processArtifactItemsRequest preprocessing instructions
154      * @return an ArrayList of preprocessed ArtifactItems
155      * @throws MojoExecutionException with a message if an error occurs
156      * @see ArtifactItem
157      */
158     protected List<ArtifactItem> getProcessedArtifactItems(ProcessArtifactItemsRequest processArtifactItemsRequest)
159             throws MojoExecutionException {
160 
161         boolean removeVersion = processArtifactItemsRequest.isRemoveVersion();
162         boolean prependGroupId = processArtifactItemsRequest.isPrependGroupId();
163         boolean useBaseVersion = processArtifactItemsRequest.isUseBaseVersion();
164 
165         boolean removeClassifier = processArtifactItemsRequest.isRemoveClassifier();
166 
167         if (artifactItems == null || artifactItems.isEmpty()) {
168             throw new MojoExecutionException("There are no artifactItems configured.");
169         }
170 
171         for (ArtifactItem artifactItem : artifactItems) {
172             this.getLog().debug("Configured Artifact: " + artifactItem.toString());
173 
174             if (artifactItem.getOutputDirectory() == null) {
175                 artifactItem.setOutputDirectory(this.outputDirectory);
176             }
177             artifactItem.getOutputDirectory().mkdirs();
178 
179             // make sure we have a version.
180             if (artifactItem.getVersion() == null || artifactItem.getVersion().isEmpty()) {
181                 fillMissingArtifactVersion(artifactItem);
182             }
183 
184             artifactItem.setArtifact(this.getArtifact(artifactItem));
185 
186             if (artifactItem.getDestFileName() == null
187                     || artifactItem.getDestFileName().isEmpty()) {
188                 artifactItem.setDestFileName(DependencyUtil.getFormattedFileName(
189                         artifactItem.getArtifact(), removeVersion, prependGroupId, useBaseVersion, removeClassifier));
190             }
191 
192             try {
193                 artifactItem.setNeedsProcessing(checkIfProcessingNeeded(artifactItem));
194             } catch (ArtifactFilterException e) {
195                 throw new MojoExecutionException(e.getMessage(), e);
196             }
197         }
198         return artifactItems;
199     }
200 
201     private boolean checkIfProcessingNeeded(ArtifactItem item) throws ArtifactFilterException {
202         return "true".equalsIgnoreCase(item.getOverWrite())
203                 || getMarkedArtifactFilter(item).isArtifactIncluded(item);
204     }
205 
206     private RepositorySystemSession createSystemSessionForLocalRepo() {
207         RepositorySystemSession repositorySystemSession = session.getRepositorySession();
208         if (localRepositoryDirectory != null) {
209             // "clone" repository session and replace localRepository
210             DefaultRepositorySystemSession newSession =
211                     new DefaultRepositorySystemSession(session.getRepositorySession());
212             // Clear cache, since we're using a new local repository
213             newSession.setCache(new DefaultRepositoryCache());
214             LocalRepositoryManager localRepositoryManager = repositorySystem.newLocalRepositoryManager(
215                     newSession, new LocalRepository(localRepositoryDirectory));
216 
217             newSession.setLocalRepositoryManager(localRepositoryManager);
218             repositorySystemSession = newSession;
219             getLog().debug("localRepoPath: "
220                     + localRepositoryManager.getRepository().getBasedir());
221         }
222 
223         return repositorySystemSession;
224     }
225 
226     /**
227      * Resolves the Artifact from the remote repository if necessary. If no version is specified, it will be retrieved
228      * from the dependency list or from the DependencyManagement section of the pom.
229      *
230      * @param artifactItem containing information about artifact from plugin configuration
231      * @return artifact object representing the specified file
232      * @throws MojoExecutionException if the version can't be found in DependencyManagement
233      */
234     protected Artifact getArtifact(ArtifactItem artifactItem) throws MojoExecutionException {
235 
236         try {
237             final String extension;
238 
239             ArtifactHandler artifactHandler = artifactHandlerManager.getArtifactHandler(artifactItem.getType());
240             if (artifactHandler != null) {
241                 extension = artifactHandler.getExtension();
242             } else {
243                 extension = artifactItem.getType();
244             }
245 
246             DefaultArtifact artifact = new DefaultArtifact(
247                     artifactItem.getGroupId(),
248                     artifactItem.getArtifactId(),
249                     artifactItem.getClassifier(),
250                     extension,
251                     artifactItem.getVersion());
252 
253             RepositorySystemSession repositorySession = createSystemSessionForLocalRepo();
254 
255             ArtifactRequest request = new ArtifactRequest(artifact, getProject().getRemoteProjectRepositories(), null);
256             ArtifactResult artifactResult = repositorySystem.resolveArtifact(repositorySession, request);
257             return RepositoryUtils.toArtifact(artifactResult.getArtifact());
258 
259         } catch (ArtifactResolutionException e) {
260             throw new MojoExecutionException("Unable to find/resolve artifact.", e);
261         }
262     }
263 
264     /**
265      * Tries to find missing version from dependency list and dependency management. If found, the artifact is updated
266      * with the correct version. It will first look for an exact match on artifactId/groupId/classifier/type and if it
267      * doesn't find a match, it will try again looking for artifactId and groupId only.
268      *
269      * @param artifact representing configured file
270      * @throws MojoExecutionException
271      */
272     private void fillMissingArtifactVersion(ArtifactItem artifact) throws MojoExecutionException {
273         MavenProject project = getProject();
274         List<Dependency> deps = project.getDependencies();
275         List<Dependency> depMngt = project.getDependencyManagement() == null
276                 ? Collections.emptyList()
277                 : project.getDependencyManagement().getDependencies();
278 
279         if (!findDependencyVersion(artifact, deps, false)
280                 && (project.getDependencyManagement() == null || !findDependencyVersion(artifact, depMngt, false))
281                 && !findDependencyVersion(artifact, deps, true)
282                 && (project.getDependencyManagement() == null || !findDependencyVersion(artifact, depMngt, true))) {
283             throw new MojoExecutionException("Unable to find artifact version of " + artifact.getGroupId() + ":"
284                     + artifact.getArtifactId() + " in either dependency list or in project's dependency management.");
285         }
286     }
287 
288     /**
289      * Tries to find missing version from a list of dependencies. If found, the artifact is updated with the correct
290      * version.
291      *
292      * @param artifact representing configured file
293      * @param dependencies list of dependencies to search
294      * @param looseMatch only look at artifactId and groupId
295      * @return the found dependency
296      */
297     private boolean findDependencyVersion(ArtifactItem artifact, List<Dependency> dependencies, boolean looseMatch) {
298         for (Dependency dependency : dependencies) {
299             if (Objects.equals(dependency.getArtifactId(), artifact.getArtifactId())
300                     && Objects.equals(dependency.getGroupId(), artifact.getGroupId())
301                     && (looseMatch || Objects.equals(dependency.getClassifier(), artifact.getClassifier()))
302                     && (looseMatch || Objects.equals(dependency.getType(), artifact.getType()))) {
303                 artifact.setVersion(dependency.getVersion());
304 
305                 return true;
306             }
307         }
308 
309         return false;
310     }
311 
312     /**
313      * @return returns the artifactItems
314      */
315     public List<ArtifactItem> getArtifactItems() {
316         return this.artifactItems;
317     }
318 
319     /**
320      * @param theArtifactItems the artifactItems to set
321      */
322     public void setArtifactItems(List<ArtifactItem> theArtifactItems) {
323         this.artifactItems = theArtifactItems;
324     }
325 
326     /**
327      * @return returns the outputDirectory
328      */
329     public File getOutputDirectory() {
330         return this.outputDirectory;
331     }
332 
333     /**
334      * @param theOutputDirectory the outputDirectory to set
335      */
336     public void setOutputDirectory(File theOutputDirectory) {
337         this.outputDirectory = theOutputDirectory;
338     }
339 
340     /**
341      * @return returns the overWriteIfNewer
342      */
343     public boolean isOverWriteIfNewer() {
344         return this.overWriteIfNewer;
345     }
346 
347     /**
348      * @param theOverWriteIfNewer the overWriteIfNewer to set
349      */
350     public void setOverWriteIfNewer(boolean theOverWriteIfNewer) {
351         this.overWriteIfNewer = theOverWriteIfNewer;
352     }
353 
354     /**
355      * @return returns the overWriteReleases
356      */
357     public boolean isOverWriteReleases() {
358         return this.overWriteReleases;
359     }
360 
361     /**
362      * @param theOverWriteReleases the overWriteReleases to set
363      */
364     public void setOverWriteReleases(boolean theOverWriteReleases) {
365         this.overWriteReleases = theOverWriteReleases;
366     }
367 
368     /**
369      * @return returns the overWriteSnapshots
370      */
371     public boolean isOverWriteSnapshots() {
372         return this.overWriteSnapshots;
373     }
374 
375     /**
376      * @param theOverWriteSnapshots the overWriteSnapshots to set
377      */
378     public void setOverWriteSnapshots(boolean theOverWriteSnapshots) {
379         this.overWriteSnapshots = theOverWriteSnapshots;
380     }
381 
382     /**
383      * @param localRepositoryDirectory {@link #localRepositoryDirectory}
384      */
385     public void setLocalRepositoryDirectory(File localRepositoryDirectory) {
386         this.localRepositoryDirectory = localRepositoryDirectory;
387     }
388 
389     /**
390      * @param artifact the artifact
391      * @throws MojoFailureException in case of an error
392      */
393     public void setArtifact(String artifact) throws MojoFailureException {
394         if (artifact != null) {
395             String packaging = "jar";
396             String classifier;
397             String[] tokens = StringUtils.split(artifact, ":");
398             if (tokens.length < 3 || tokens.length > 5) {
399                 throw new MojoFailureException("Invalid artifact, "
400                         + "you must specify groupId:artifactId:version[:packaging[:classifier]] " + artifact);
401             }
402             String groupId = tokens[0];
403             String artifactId = tokens[1];
404             String version = tokens[2];
405             if (tokens.length >= 4) {
406                 packaging = tokens[3];
407             }
408             if (tokens.length == 5) {
409                 classifier = tokens[4];
410             } else {
411                 classifier = null;
412             }
413 
414             ArtifactItem artifactItem = new ArtifactItem();
415             artifactItem.setGroupId(groupId);
416             artifactItem.setArtifactId(artifactId);
417             artifactItem.setVersion(version);
418             artifactItem.setType(packaging);
419             artifactItem.setClassifier(classifier);
420 
421             setArtifactItems(Collections.singletonList(artifactItem));
422         }
423     }
424 }