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.resolver.internal.ant;
20  
21  import java.io.BufferedReader;
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.InputStreamReader;
25  import java.nio.charset.StandardCharsets;
26  import java.nio.file.Files;
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.Date;
31  import java.util.HashMap;
32  import java.util.HashSet;
33  import java.util.LinkedHashMap;
34  import java.util.List;
35  import java.util.Locale;
36  import java.util.Map;
37  import java.util.Objects;
38  import java.util.Properties;
39  import java.util.concurrent.CopyOnWriteArrayList;
40  
41  import org.apache.maven.model.Model;
42  import org.apache.maven.model.building.DefaultModelBuildingRequest;
43  import org.apache.maven.model.building.FileModelSource;
44  import org.apache.maven.model.building.ModelBuildingException;
45  import org.apache.maven.model.building.ModelBuildingRequest;
46  import org.apache.maven.model.resolution.ModelResolver;
47  import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
48  import org.apache.maven.resolver.internal.ant.types.Artifact;
49  import org.apache.maven.resolver.internal.ant.types.Artifacts;
50  import org.apache.maven.resolver.internal.ant.types.Authentication;
51  import org.apache.maven.resolver.internal.ant.types.Dependencies;
52  import org.apache.maven.resolver.internal.ant.types.Dependency;
53  import org.apache.maven.resolver.internal.ant.types.DependencyContainer;
54  import org.apache.maven.resolver.internal.ant.types.Exclusion;
55  import org.apache.maven.resolver.internal.ant.types.LocalRepository;
56  import org.apache.maven.resolver.internal.ant.types.Mirror;
57  import org.apache.maven.resolver.internal.ant.types.Pom;
58  import org.apache.maven.resolver.internal.ant.types.Proxy;
59  import org.apache.maven.resolver.internal.ant.types.RemoteRepositories;
60  import org.apache.maven.resolver.internal.ant.types.RemoteRepository;
61  import org.apache.maven.resolver.internal.ant.types.RemoteRepository.Policy;
62  import org.apache.maven.settings.Profile;
63  import org.apache.maven.settings.Repository;
64  import org.apache.maven.settings.RepositoryPolicy;
65  import org.apache.maven.settings.Server;
66  import org.apache.maven.settings.Settings;
67  import org.apache.maven.settings.building.DefaultSettingsBuilderFactory;
68  import org.apache.maven.settings.building.DefaultSettingsBuildingRequest;
69  import org.apache.maven.settings.building.SettingsBuilder;
70  import org.apache.maven.settings.building.SettingsBuildingException;
71  import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
72  import org.apache.maven.settings.crypto.SettingsDecrypter;
73  import org.apache.maven.settings.crypto.SettingsDecryptionResult;
74  import org.apache.tools.ant.BuildException;
75  import org.apache.tools.ant.Project;
76  import org.apache.tools.ant.Task;
77  import org.apache.tools.ant.taskdefs.condition.Os;
78  import org.apache.tools.ant.types.Reference;
79  import org.codehaus.plexus.util.xml.Xpp3Dom;
80  import org.eclipse.aether.ConfigurationProperties;
81  import org.eclipse.aether.DefaultRepositoryCache;
82  import org.eclipse.aether.DefaultRepositorySystemSession;
83  import org.eclipse.aether.RepositorySystem;
84  import org.eclipse.aether.RepositorySystemSession;
85  import org.eclipse.aether.artifact.DefaultArtifact;
86  import org.eclipse.aether.collection.CollectRequest;
87  import org.eclipse.aether.collection.CollectResult;
88  import org.eclipse.aether.collection.DependencyCollectionException;
89  import org.eclipse.aether.deployment.DeployRequest;
90  import org.eclipse.aether.deployment.DeploymentException;
91  import org.eclipse.aether.impl.RemoteRepositoryManager;
92  import org.eclipse.aether.installation.InstallRequest;
93  import org.eclipse.aether.installation.InstallationException;
94  import org.eclipse.aether.repository.AuthenticationSelector;
95  import org.eclipse.aether.repository.LocalRepositoryManager;
96  import org.eclipse.aether.repository.MirrorSelector;
97  import org.eclipse.aether.repository.ProxySelector;
98  import org.eclipse.aether.util.repository.AuthenticationBuilder;
99  import org.eclipse.aether.util.repository.ConservativeAuthenticationSelector;
100 import org.eclipse.aether.util.repository.DefaultAuthenticationSelector;
101 import org.eclipse.aether.util.repository.DefaultMirrorSelector;
102 import org.eclipse.aether.util.repository.DefaultProxySelector;
103 
104 /**
105  * Central utility for managing Maven repository system configuration and sessions within Ant builds.
106  * <p>
107  * This class handles the lifecycle of a {@link RepositorySystem} and provides access to its components,
108  * such as mirror selectors, proxy selectors, authentication, session management, and settings resolution.
109  * It also provides methods for resolving dependencies, installing, and deploying artifacts.
110  * </p>
111  *
112  * <p>
113  * An instance of this class is typically retrieved via {@link #getInstance(Project)} and registered under
114  * a predefined reference ID for reuse throughout the Ant build.
115  * </p>
116  *
117  * <p>
118  * This class supports merging default and user-defined repositories and processing settings from
119  * {@code settings.xml} files, including mirrors, proxies, and server credentials.
120  * </p>
121  *
122  */
123 public class AntRepoSys {
124     private static final Date STARTED = new Date();
125 
126     private static final boolean OS_WINDOWS = Os.isFamily("windows");
127 
128     private static final SettingsBuilder SETTINGS_BUILDER = new DefaultSettingsBuilderFactory().newInstance();
129 
130     private static final SettingsDecrypter SETTINGS_DECRYPTER = new AntSettingsDecryptorFactory().newInstance();
131 
132     private final Project project;
133 
134     private final AntRepositorySystemSupplier antRepositorySystemSupplier;
135 
136     private final RepositorySystem repoSys;
137 
138     private File userSettings;
139 
140     private File globalSettings;
141 
142     private Settings settings;
143 
144     private final List<Mirror> mirrors = new CopyOnWriteArrayList<>();
145 
146     private final List<Proxy> proxies = new CopyOnWriteArrayList<>();
147 
148     private final List<Authentication> authentications = new CopyOnWriteArrayList<>();
149 
150     private LocalRepository localRepository;
151 
152     private Pom defaultPom;
153 
154     private static <T> boolean eq(T o1, T o2) {
155         return Objects.equals(o1, o2);
156     }
157 
158     /**
159      * Returns the singleton instance of {@code AntRepoSys} associated with the given Ant {@code Project}.
160      * Registers a new instance if not already present.
161      *
162      * @param project the current Ant project
163      * @return the {@code AntRepoSys} instance
164      */
165     public static synchronized AntRepoSys getInstance(Project project) {
166         Object obj = project.getReference(Names.ID);
167         if (obj instanceof AntRepoSys) {
168             return (AntRepoSys) obj;
169         }
170         AntRepoSys instance = new AntRepoSys(project);
171         project.addReference(Names.ID, instance);
172         instance.initDefaults();
173         return instance;
174     }
175 
176     private AntRepoSys(Project project) {
177         this.project = project;
178         this.antRepositorySystemSupplier = new AntRepositorySystemSupplier();
179         this.repoSys = antRepositorySystemSupplier.get();
180     }
181 
182     private void initDefaults() {
183         RemoteRepository repo = new RemoteRepository();
184         repo.setProject(project);
185         repo.setId("central");
186         repo.setUrl("https://repo.maven.apache.org/maven2/");
187         project.addReference(Names.ID_CENTRAL, repo);
188 
189         repo = new RemoteRepository();
190         repo.setProject(project);
191         repo.setRefid(new Reference(project, Names.ID_CENTRAL));
192 
193         RemoteRepositories repos = new RemoteRepositories();
194         repos.setProject(project);
195         repos.addRemoterepo(repo);
196         project.addReference(Names.ID_DEFAULT_REPOS, repos);
197     }
198 
199     /**
200      * Returns the {@link RepositorySystem} used for dependency resolution and repository operations.
201      *
202      * @return the repository system instance
203      */
204     public synchronized RepositorySystem getSystem() {
205         return repoSys;
206     }
207 
208     private synchronized RemoteRepositoryManager getRemoteRepoMan() {
209         return antRepositorySystemSupplier.remoteRepositoryManager;
210     }
211 
212     /**
213      * Creates and returns a new {@link RepositorySystemSession} for the given task and local repository.
214      * Configures authentication, mirrors, proxies, offline mode, and repository listeners.
215      *
216      * @param task the invoking Ant task (used for logging and listeners)
217      * @param localRepo optional local repository configuration
218      * @return a configured repository system session
219      */
220     public RepositorySystemSession getSession(Task task, LocalRepository localRepo) {
221         DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
222 
223         final Map<Object, Object> configProps = new LinkedHashMap<>();
224         configProps.put(ConfigurationProperties.USER_AGENT, getUserAgent());
225         configProps.put("maven.startTime", STARTED);
226         configProps.putAll(getSystemProperties());
227         configProps.putAll(getUserProperties());
228         processServerConfiguration(configProps);
229 
230         session.setConfigProperties(configProps);
231         session.setSystemProperties(getSystemProperties());
232         session.setUserProperties(getUserProperties());
233         session.setOffline(isOffline());
234 
235         session.setProxySelector(getProxySelector());
236         session.setMirrorSelector(getMirrorSelector());
237         session.setAuthenticationSelector(getAuthSelector());
238 
239         session.setCache(new DefaultRepositoryCache());
240 
241         session.setRepositoryListener(new AntRepositoryListener(task));
242         session.setTransferListener(new AntTransferListener(task));
243 
244         session.setLocalRepositoryManager(getLocalRepoMan(session, localRepo));
245 
246         session.setWorkspaceReader(ProjectWorkspaceReader.getInstance());
247 
248         return session;
249     }
250 
251     private String getUserAgent() {
252         return "Apache-Ant/" + project.getProperty("ant.version")
253                 + " ("
254                 + "Java " + System.getProperty("java.version")
255                 + "; "
256                 + System.getProperty("os.name") + " " + System.getProperty("os.version")
257                 + ")"
258                 + " Aether";
259     }
260 
261     private boolean isOffline() {
262         String prop = project.getProperty(Names.PROPERTY_OFFLINE);
263         if (prop != null) {
264             return Boolean.parseBoolean(prop);
265         }
266         return getSettings().isOffline();
267     }
268 
269     private void processServerConfiguration(Map<Object, Object> configProps) {
270         Settings settings = getSettings();
271         for (Server server : settings.getServers()) {
272             if (server.getConfiguration() != null) {
273                 Xpp3Dom dom = (Xpp3Dom) server.getConfiguration();
274                 for (int i = dom.getChildCount() - 1; i >= 0; i--) {
275                     Xpp3Dom child = dom.getChild(i);
276                     if ("wagonProvider".equals(child.getName())) {
277                         dom.removeChild(i);
278                     } else if ("httpHeaders".equals(child.getName())) {
279                         configProps.put(
280                                 ConfigurationProperties.HTTP_HEADERS + "." + server.getId(), getHttpHeaders(child));
281                     }
282                 }
283 
284                 configProps.put("aether.connector.wagon.config." + server.getId(), dom);
285             }
286 
287             configProps.put("aether.connector.perms.fileMode." + server.getId(), server.getFilePermissions());
288             configProps.put("aether.connector.perms.dirMode." + server.getId(), server.getDirectoryPermissions());
289         }
290     }
291 
292     private Map<String, String> getHttpHeaders(Xpp3Dom dom) {
293         final Map<String, String> headers = new HashMap<>();
294         for (int i = 0; i < dom.getChildCount(); i++) {
295             Xpp3Dom child = dom.getChild(i);
296             Xpp3Dom name = child.getChild("name");
297             Xpp3Dom value = child.getChild("value");
298             if (name != null && name.getValue() != null) {
299                 headers.put(name.getValue(), (value != null) ? value.getValue() : null);
300             }
301         }
302         return Collections.unmodifiableMap(headers);
303     }
304 
305     private File getDefaultLocalRepoDir() {
306         String dir = project.getProperty("maven.repo.local");
307         if (dir != null) {
308             return project.resolveFile(dir);
309         }
310 
311         Settings settings = getSettings();
312         if (settings.getLocalRepository() != null) {
313             return new File(settings.getLocalRepository());
314         }
315 
316         return new File(new File(project.getProperty("user.home"), ".m2"), "repository");
317     }
318 
319     private LocalRepositoryManager getLocalRepoMan(RepositorySystemSession session, LocalRepository localRepo) {
320         if (localRepo == null) {
321             localRepo = localRepository;
322         }
323 
324         File repoDir;
325         if (localRepo != null && localRepo.getDir() != null) {
326             repoDir = localRepo.getDir();
327         } else {
328             repoDir = getDefaultLocalRepoDir();
329         }
330 
331         org.eclipse.aether.repository.LocalRepository repo = new org.eclipse.aether.repository.LocalRepository(repoDir);
332 
333         return getSystem().newLocalRepositoryManager(session, repo);
334     }
335 
336     private synchronized Settings getSettings() {
337         if (settings == null) {
338             DefaultSettingsBuildingRequest request = new DefaultSettingsBuildingRequest();
339             request.setUserSettingsFile(getUserSettings());
340             request.setGlobalSettingsFile(getGlobalSettings());
341             request.setSystemProperties(getSystemProperties());
342             request.setUserProperties(getUserProperties());
343 
344             try {
345                 settings = SETTINGS_BUILDER.build(request).getEffectiveSettings();
346             } catch (SettingsBuildingException e) {
347                 project.log("Could not process settings.xml: " + e.getMessage(), e, Project.MSG_WARN);
348             }
349 
350             SettingsDecryptionResult result =
351                     SETTINGS_DECRYPTER.decrypt(new DefaultSettingsDecryptionRequest(settings));
352             settings.setServers(result.getServers());
353             settings.setProxies(result.getProxies());
354         }
355         return settings;
356     }
357 
358     private ProxySelector getProxySelector() {
359         DefaultProxySelector selector = new DefaultProxySelector();
360 
361         for (Proxy proxy : proxies) {
362             selector.add(ConverterUtils.toProxy(proxy), proxy.getNonProxyHosts());
363         }
364 
365         Settings settings = getSettings();
366         for (org.apache.maven.settings.Proxy proxy : settings.getProxies()) {
367             AuthenticationBuilder auth = new AuthenticationBuilder();
368             auth.addUsername(proxy.getUsername()).addPassword(proxy.getPassword());
369             selector.add(
370                     new org.eclipse.aether.repository.Proxy(
371                             proxy.getProtocol(), proxy.getHost(),
372                             proxy.getPort(), auth.build()),
373                     proxy.getNonProxyHosts());
374         }
375 
376         return selector;
377     }
378 
379     private MirrorSelector getMirrorSelector() {
380         DefaultMirrorSelector selector = new DefaultMirrorSelector();
381 
382         for (Mirror mirror : mirrors) {
383             selector.add(mirror.getId(), mirror.getUrl(), mirror.getType(), false, false, mirror.getMirrorOf(), null);
384         }
385 
386         Settings settings = getSettings();
387         for (org.apache.maven.settings.Mirror mirror : settings.getMirrors()) {
388             selector.add(
389                     String.valueOf(mirror.getId()),
390                     mirror.getUrl(),
391                     mirror.getLayout(),
392                     false,
393                     false,
394                     mirror.getMirrorOf(),
395                     mirror.getMirrorOfLayouts());
396         }
397 
398         return selector;
399     }
400 
401     private AuthenticationSelector getAuthSelector() {
402         DefaultAuthenticationSelector selector = new DefaultAuthenticationSelector();
403 
404         final Collection<String> ids = new HashSet<>();
405         for (Authentication auth : authentications) {
406             List<String> servers = auth.getServers();
407             if (!servers.isEmpty()) {
408                 org.eclipse.aether.repository.Authentication a = ConverterUtils.toAuthentication(auth);
409                 for (String server : servers) {
410                     if (ids.add(server)) {
411                         selector.add(server, a);
412                     }
413                 }
414             }
415         }
416 
417         Settings settings = getSettings();
418         for (Server server : settings.getServers()) {
419             AuthenticationBuilder auth = new AuthenticationBuilder();
420             auth.addUsername(server.getUsername()).addPassword(server.getPassword());
421             auth.addPrivateKey(server.getPrivateKey(), server.getPassphrase());
422             selector.add(server.getId(), auth.build());
423         }
424 
425         return new ConservativeAuthenticationSelector(selector);
426     }
427 
428     private RemoteRepositories getRemoteRepositories() {
429         RemoteRepositories remoteRepositories = new RemoteRepositories();
430         remoteRepositories.setProject(project);
431 
432         Settings settings = getSettings();
433         List<String> activeProfiles = settings.getActiveProfiles();
434         for (String profileId : activeProfiles) {
435             Profile profile = settings.getProfilesAsMap().get(profileId);
436             for (Repository repository : profile.getRepositories()) {
437                 String id = repository.getId();
438                 RemoteRepository repo = new RemoteRepository();
439                 repo.setProject(project);
440                 repo.setId(id);
441                 repo.setUrl(repository.getUrl());
442                 if (repository.getReleases() != null) {
443                     RepositoryPolicy repositoryPolicy = repository.getReleases();
444                     Policy policy = new Policy();
445                     policy.setEnabled(repositoryPolicy.isEnabled());
446                     if (repositoryPolicy.getChecksumPolicy() != null) {
447                         policy.setChecksums(repositoryPolicy.getChecksumPolicy());
448                     }
449                     if (repositoryPolicy.getUpdatePolicy() != null) {
450                         policy.setUpdates(repositoryPolicy.getUpdatePolicy());
451                     }
452                     repo.addReleases(policy);
453                 }
454                 if (repository.getSnapshots() != null) {
455                     RepositoryPolicy repositoryPolicy = repository.getSnapshots();
456                     Policy policy = new Policy();
457                     policy.setEnabled(repositoryPolicy.isEnabled());
458                     if (repositoryPolicy.getChecksumPolicy() != null) {
459                         policy.setChecksums(repositoryPolicy.getChecksumPolicy());
460                     }
461                     if (repositoryPolicy.getUpdatePolicy() != null) {
462                         policy.setUpdates(repositoryPolicy.getUpdatePolicy());
463                     }
464                     repo.addSnapshots(policy);
465                 }
466                 project.addReference(id, repo);
467 
468                 repo = new RemoteRepository();
469                 repo.setProject(project);
470                 repo.setRefid(new Reference(project, id));
471                 remoteRepositories.addRemoterepo(repo);
472             }
473         }
474 
475         return remoteRepositories;
476     }
477 
478     private RemoteRepositories getMergedRepositories() {
479         RemoteRepositories defaultRepositories = AetherUtils.getDefaultRepositories(project);
480         RemoteRepositories settingsRepositories = getRemoteRepositories();
481 
482         RemoteRepositories mergedRepositories = new RemoteRepositories();
483         mergedRepositories.setProject(project);
484         mergedRepositories.addRemoterepos(defaultRepositories);
485         mergedRepositories.addRemoterepos(settingsRepositories);
486 
487         return mergedRepositories;
488     }
489 
490     /**
491      * Sets the path to the user-level {@code settings.xml} file. If changed, forces reloading of settings.
492      *
493      * @param file the user settings file
494      */
495     public synchronized void setUserSettings(File file) {
496         if (!eq(this.userSettings, file)) {
497             settings = null;
498         }
499         this.userSettings = file;
500     }
501 
502     /**
503      * Returns the resolved user-level {@code settings.xml} file path.
504      * Falls back to the default lookup strategy if not explicitly set.
505      *
506      * @return the user settings file
507      */
508     /* UT */ File getUserSettings() {
509         if (userSettings == null) {
510             userSettings = AetherUtils.findUserSettings(project);
511         }
512         return userSettings;
513     }
514 
515     /**
516      * Sets the path to the global {@code settings.xml} file. If changed, forces reloading of settings.
517      *
518      * @param file the global settings file
519      */
520     public void setGlobalSettings(File file) {
521         if (!eq(this.globalSettings, file)) {
522             settings = null;
523         }
524         this.globalSettings = file;
525     }
526 
527     /**
528      * Returns the resolved global {@code settings.xml} file path.
529      * Falls back to the default lookup strategy if not explicitly set.
530      *
531      * @return the global settings file
532      */
533     /* UT */ File getGlobalSettings() {
534         if (globalSettings == null) {
535             globalSettings = AetherUtils.findGlobalSettings(project);
536         }
537         return globalSettings;
538     }
539 
540     /**
541      * Registers a {@link Proxy} to be considered when building the proxy selector.
542      *
543      * @param proxy the proxy configuration
544      */
545     public void addProxy(Proxy proxy) {
546         proxies.add(proxy);
547     }
548 
549     /**
550      * Registers a {@link Mirror} to be included in the mirror selector.
551      *
552      * @param mirror the mirror configuration
553      */
554     public void addMirror(Mirror mirror) {
555         mirrors.add(mirror);
556     }
557 
558     /**
559      * Registers an {@link Authentication} instance to be used during authentication selection.
560      *
561      * @param authentication the authentication configuration
562      */
563     public void addAuthentication(Authentication authentication) {
564         authentications.add(authentication);
565     }
566 
567     /**
568      * Sets the local repository configuration.
569      *
570      * @param localRepository the local repository configuration
571      */
572     public void setLocalRepository(LocalRepository localRepository) {
573         this.localRepository = localRepository;
574     }
575 
576     /**
577      * Resolves a {@link Model} from a {@code pom.xml} file, optionally validating it as a local or remote model.
578      *
579      * @param task the Ant task context
580      * @param pomFile the POM file
581      * @param local whether to load the model in strict/local mode
582      * @param remoteRepositories optional custom remote repositories
583      * @return the effective Maven model
584      * @throws BuildException if the POM cannot be loaded or resolved
585      */
586     public Model loadModel(Task task, File pomFile, boolean local, RemoteRepositories remoteRepositories) {
587         RepositorySystemSession session = getSession(task, null);
588 
589         remoteRepositories = remoteRepositories == null ? getMergedRepositories() : remoteRepositories;
590 
591         List<org.eclipse.aether.repository.RemoteRepository> repositories =
592                 ConverterUtils.toRepositories(task.getProject(), getSystem(), session, remoteRepositories);
593 
594         ModelResolver modelResolver =
595                 new AntModelResolver(session, "project", getSystem(), getRemoteRepoMan(), repositories);
596 
597         Settings settings = getSettings();
598 
599         try {
600             DefaultModelBuildingRequest request = new DefaultModelBuildingRequest();
601             request.setLocationTracking(true);
602             request.setProcessPlugins(false);
603             if (local) {
604                 request.setPomFile(pomFile);
605                 request.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_STRICT);
606             } else {
607                 request.setModelSource(new FileModelSource(pomFile));
608                 request.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);
609             }
610             request.setSystemProperties(getSystemProperties());
611             request.setUserProperties(getUserProperties());
612             request.setProfiles(SettingsUtils.convert(settings.getProfiles()));
613             request.setActiveProfileIds(settings.getActiveProfiles());
614             request.setModelResolver(modelResolver);
615             return antRepositorySystemSupplier.modelBuilder.build(request).getEffectiveModel();
616         } catch (ModelBuildingException e) {
617             throw new BuildException("Could not load POM " + pomFile + ": " + e.getMessage(), e);
618         }
619     }
620 
621     private Properties getSystemProperties() {
622         Properties props = new Properties();
623         getEnvProperties(props);
624         props.putAll(System.getProperties());
625         ConverterUtils.addProperties(props, project.getProperties());
626         return props;
627     }
628 
629     private void getEnvProperties(Properties props) {
630         if (props == null) {
631             props = new Properties();
632         }
633         for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
634             String key = entry.getKey();
635             if (OS_WINDOWS) {
636                 key = key.toUpperCase(Locale.ENGLISH);
637             }
638             key = "env." + key;
639             props.put(key, entry.getValue());
640         }
641     }
642 
643     private Properties getUserProperties() {
644         return ConverterUtils.addProperties(null, project.getUserProperties());
645     }
646 
647     /**
648      * Sets the default {@link Pom} used for artifact operations when none is explicitly provided.
649      *
650      * @param pom the default POM
651      */
652     public void setDefaultPom(Pom pom) {
653         this.defaultPom = pom;
654     }
655 
656     /**
657      * Returns the default {@link Pom}, if set.
658      *
659      * @return the default POM or {@code null}
660      */
661     public Pom getDefaultPom() {
662         return defaultPom;
663     }
664 
665     /**
666      * Performs dependency resolution by collecting transitive dependencies for the given configuration.
667      *
668      * @param task the Ant task context
669      * @param dependencies the root dependencies
670      * @param localRepository optional local repository override
671      * @param remoteRepositories optional custom remote repositories
672      * @return the result of dependency collection
673      * @throws BuildException if the dependency collection fails
674      */
675     public CollectResult collectDependencies(
676             Task task,
677             Dependencies dependencies,
678             LocalRepository localRepository,
679             RemoteRepositories remoteRepositories) {
680         RepositorySystemSession session = getSession(task, localRepository);
681 
682         remoteRepositories = remoteRepositories == null ? getMergedRepositories() : remoteRepositories;
683 
684         List<org.eclipse.aether.repository.RemoteRepository> repos =
685                 ConverterUtils.toRepositories(project, getSystem(), session, remoteRepositories);
686 
687         CollectRequest collectRequest = new CollectRequest();
688         collectRequest.setRequestContext("project");
689 
690         for (org.eclipse.aether.repository.RemoteRepository repo : repos) {
691             task.getProject().log("Using remote repository " + repo, Project.MSG_VERBOSE);
692             collectRequest.addRepository(repo);
693         }
694 
695         if (dependencies != null) {
696             populateCollectRequest(collectRequest, task, session, dependencies, Collections.emptyList());
697         }
698 
699         task.getProject().log("Collecting dependencies", Project.MSG_VERBOSE);
700 
701         CollectResult result;
702         try {
703             result = getSystem().collectDependencies(session, collectRequest);
704         } catch (DependencyCollectionException e) {
705             throw new BuildException("Could not collect dependencies: " + e.getMessage(), e);
706         }
707 
708         return result;
709     }
710 
711     private void populateCollectRequest(
712             CollectRequest collectRequest,
713             Task task,
714             RepositorySystemSession session,
715             Dependencies dependencies,
716             List<Exclusion> exclusions) {
717         List<Exclusion> globalExclusions = exclusions;
718         if (!dependencies.getExclusions().isEmpty()) {
719             globalExclusions = new ArrayList<>(exclusions);
720             globalExclusions.addAll(dependencies.getExclusions());
721         }
722 
723         final Collection<String> ids = new HashSet<>();
724 
725         for (DependencyContainer container : dependencies.getDependencyContainers()) {
726             if (container instanceof Dependency) {
727                 Dependency dep = (Dependency) container;
728                 ids.add(dep.getVersionlessKey());
729                 collectRequest.addDependency(ConverterUtils.toDependency(dep, globalExclusions, session));
730             } else {
731                 populateCollectRequest(collectRequest, task, session, (Dependencies) container, globalExclusions);
732             }
733         }
734 
735         if (dependencies.getPom() != null) {
736             Model model = dependencies.getPom().getModel(task);
737             if (model.getDependencyManagement() != null) {
738                 for (org.apache.maven.model.Dependency manDep :
739                         model.getDependencyManagement().getDependencies()) {
740                     Dependency dependency = new Dependency();
741                     dependency.setArtifactId(manDep.getArtifactId());
742                     dependency.setClassifier(manDep.getClassifier());
743                     dependency.setGroupId(manDep.getGroupId());
744                     dependency.setScope(manDep.getScope());
745                     dependency.setType(manDep.getType());
746                     dependency.setVersion(manDep.getVersion());
747                     if (manDep.getSystemPath() != null
748                             && !manDep.getSystemPath().isEmpty()) {
749                         dependency.setSystemPath(task.getProject().resolveFile(manDep.getSystemPath()));
750                     }
751                     for (org.apache.maven.model.Exclusion exc : manDep.getExclusions()) {
752                         Exclusion exclusion = new Exclusion();
753                         exclusion.setGroupId(exc.getGroupId());
754                         exclusion.setArtifactId(exc.getArtifactId());
755                         exclusion.setClassifier("*");
756                         exclusion.setExtension("*");
757                         dependency.addExclusion(exclusion);
758                     }
759                     collectRequest.addManagedDependency(
760                             ConverterUtils.toManagedDependency(dependency, globalExclusions, session));
761                 }
762             }
763 
764             for (org.apache.maven.model.Dependency dep : model.getDependencies()) {
765                 Dependency dependency = new Dependency();
766                 dependency.setArtifactId(dep.getArtifactId());
767                 dependency.setClassifier(dep.getClassifier());
768                 dependency.setGroupId(dep.getGroupId());
769                 dependency.setScope(dep.getScope());
770                 dependency.setType(dep.getType());
771                 dependency.setVersion(dep.getVersion());
772                 if (ids.contains(dependency.getVersionlessKey())) {
773                     project.log(
774                             "Ignoring dependency " + dependency.getVersionlessKey() + " from " + model.getId()
775                                     + ", already declared locally",
776                             Project.MSG_VERBOSE);
777                     continue;
778                 }
779                 if (dep.getSystemPath() != null && !dep.getSystemPath().isEmpty()) {
780                     dependency.setSystemPath(task.getProject().resolveFile(dep.getSystemPath()));
781                 }
782                 for (org.apache.maven.model.Exclusion exc : dep.getExclusions()) {
783                     Exclusion exclusion = new Exclusion();
784                     exclusion.setGroupId(exc.getGroupId());
785                     exclusion.setArtifactId(exc.getArtifactId());
786                     exclusion.setClassifier("*");
787                     exclusion.setExtension("*");
788                     dependency.addExclusion(exclusion);
789                 }
790                 collectRequest.addDependency(ConverterUtils.toDependency(dependency, globalExclusions, session));
791             }
792         }
793 
794         if (dependencies.getFile() != null) {
795             List<Dependency> deps = readDependencies(dependencies.getFile());
796             for (Dependency dependency : deps) {
797                 if (ids.contains(dependency.getVersionlessKey())) {
798                     project.log(
799                             "Ignoring dependency " + dependency.getVersionlessKey() + " from " + dependencies.getFile()
800                                     + ", already declared locally",
801                             Project.MSG_VERBOSE);
802                     continue;
803                 }
804                 collectRequest.addDependency(ConverterUtils.toDependency(dependency, globalExclusions, session));
805             }
806         }
807     }
808 
809     private List<Dependency> readDependencies(File file) {
810         final List<Dependency> dependencies = new ArrayList<>();
811         try {
812             try (BufferedReader reader = new BufferedReader(
813                     new InputStreamReader(Files.newInputStream(file.toPath()), StandardCharsets.UTF_8))) {
814                 for (String line = reader.readLine(); line != null; line = reader.readLine()) {
815                     int comment = line.indexOf('#');
816                     if (comment >= 0) {
817                         line = line.substring(0, comment);
818                     }
819                     line = line.trim();
820                     if (line.length() <= 0) {
821                         continue;
822                     }
823                     Dependency dependency = new Dependency();
824                     dependency.setCoords(line);
825                     dependencies.add(dependency);
826                 }
827             }
828         } catch (IOException e) {
829             throw new BuildException("Cannot read " + file, e);
830         }
831         return dependencies;
832     }
833 
834     /**
835      * Installs the specified artifacts to the local Maven repository.
836      *
837      * @param task the Ant task context
838      * @param pom the associated POM metadata
839      * @param artifacts the artifacts to install
840      * @throws BuildException if the installation fails
841      */
842     public void install(Task task, Pom pom, Artifacts artifacts) {
843         RepositorySystemSession session = getSession(task, null);
844 
845         InstallRequest request = new InstallRequest();
846         request.setArtifacts(toArtifacts(task, session, pom, artifacts));
847 
848         try {
849             getSystem().install(session, request);
850         } catch (InstallationException e) {
851             throw new BuildException("Could not install artifacts: " + e.getMessage(), e);
852         }
853     }
854 
855     /**
856      * Deploys the specified artifacts to the configured remote repository (release or snapshot).
857      *
858      * @param task the Ant task context
859      * @param pom the associated POM metadata
860      * @param artifacts the artifacts to deploy
861      * @param releaseRepository the repository for release artifacts
862      * @param snapshotRepository the repository for snapshot artifacts
863      * @throws BuildException if the deployment fails
864      */
865     public void deploy(
866             Task task,
867             Pom pom,
868             Artifacts artifacts,
869             RemoteRepository releaseRepository,
870             RemoteRepository snapshotRepository) {
871         RepositorySystemSession session = getSession(task, null);
872 
873         DeployRequest request = new DeployRequest();
874         request.setArtifacts(toArtifacts(task, session, pom, artifacts));
875         boolean snapshot = request.getArtifacts().iterator().next().isSnapshot();
876         RemoteRepository distRepo = (snapshot && snapshotRepository != null) ? snapshotRepository : releaseRepository;
877         request.setRepository(ConverterUtils.toDistRepository(distRepo, session));
878 
879         try {
880             getSystem().deploy(session, request);
881         } catch (DeploymentException e) {
882             throw new BuildException("Could not deploy artifacts: " + e.getMessage(), e);
883         }
884     }
885 
886     private List<org.eclipse.aether.artifact.Artifact> toArtifacts(
887             Task task, RepositorySystemSession session, Pom pom, Artifacts artifacts) {
888         Model model = pom.getModel(task);
889         File pomFile = pom.getFile();
890 
891         final List<org.eclipse.aether.artifact.Artifact> results = new ArrayList<>();
892 
893         org.eclipse.aether.artifact.Artifact pomArtifact = new DefaultArtifact(
894                         model.getGroupId(), model.getArtifactId(), "pom", model.getVersion())
895                 .setFile(pomFile);
896         results.add(pomArtifact);
897 
898         for (Artifact artifact : artifacts.getArtifacts()) {
899             org.eclipse.aether.artifact.Artifact buildArtifact = new DefaultArtifact(
900                             model.getGroupId(),
901                             model.getArtifactId(),
902                             artifact.getClassifier(),
903                             artifact.getType(),
904                             model.getVersion())
905                     .setFile(artifact.getFile());
906             results.add(buildArtifact);
907         }
908 
909         return results;
910     }
911 }