1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package de.softwareforge.testing.maven;
16
17 import static java.lang.String.format;
18 import static java.util.Objects.requireNonNull;
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Objects;
27 import java.util.SortedSet;
28 import java.util.TreeSet;
29
30 import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
31 import org.apache.maven.settings.Profile;
32 import org.apache.maven.settings.Repository;
33 import org.apache.maven.settings.Settings;
34 import org.apache.maven.settings.building.DefaultSettingsBuilder;
35 import org.apache.maven.settings.building.DefaultSettingsBuilderFactory;
36 import org.apache.maven.settings.building.DefaultSettingsBuildingRequest;
37 import org.apache.maven.settings.building.SettingsBuildingException;
38 import org.apache.maven.settings.building.SettingsBuildingRequest;
39 import org.apache.maven.settings.building.SettingsBuildingResult;
40 import org.eclipse.aether.DefaultRepositorySystemSession;
41 import org.eclipse.aether.RepositoryException;
42 import org.eclipse.aether.RepositorySystem;
43 import org.eclipse.aether.RepositorySystemSession;
44 import org.eclipse.aether.artifact.Artifact;
45 import org.eclipse.aether.artifact.DefaultArtifact;
46 import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
47 import org.eclipse.aether.impl.DefaultServiceLocator;
48 import org.eclipse.aether.repository.LocalRepository;
49 import org.eclipse.aether.repository.RemoteRepository;
50 import org.eclipse.aether.resolution.ArtifactRequest;
51 import org.eclipse.aether.resolution.ArtifactResult;
52 import org.eclipse.aether.resolution.VersionRangeRequest;
53 import org.eclipse.aether.resolution.VersionRangeResolutionException;
54 import org.eclipse.aether.resolution.VersionRangeResult;
55 import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
56 import org.eclipse.aether.spi.connector.transport.TransporterFactory;
57 import org.eclipse.aether.spi.locator.ServiceLocator;
58 import org.eclipse.aether.transport.file.FileTransporterFactory;
59 import org.eclipse.aether.transport.http.HttpTransporterFactory;
60 import org.eclipse.aether.version.Version;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63
64
65
66
67
68
69
70 public final class MavenArtifactLoader {
71
72 private static final Logger LOG = LoggerFactory.getLogger(MavenArtifactLoader.class);
73
74 private static final RemoteRepository CENTRAL_REPO = new RemoteRepository.Builder("central", "default", "https://repo.maven.apache.org/maven2/").build();
75
76 private static final List<RemoteRepository> knownRemoteRepos = List.of(
77 CENTRAL_REPO,
78 new RemoteRepository.Builder("snapshots", "default", "https://central.sonatype.com/repository/maven-snapshots/").build()
79 );
80
81 private static final String USER_HOME = System.getProperty("user.home");
82 private static final File USER_MAVEN_HOME = new File(USER_HOME, ".m2");
83 private static final String ENV_M2_HOME = System.getenv("M2_HOME");
84
85 private static final File DEFAULT_USER_SETTINGS_FILE = new File(USER_MAVEN_HOME, "settings.xml");
86 private static final File DEFAULT_USER_REPOSITORY = new File(USER_MAVEN_HOME, "repository");
87 private static final File DEFAULT_GLOBAL_SETTINGS_FILE =
88 new File(System.getProperty("maven.home", Objects.requireNonNullElse(ENV_M2_HOME, "")), "conf/settings.xml");
89
90 private final RepositorySystem repositorySystem;
91 private final RepositorySystemSession mavenSession;
92 private final List<RemoteRepository> remoteRepositories;
93
94 private final String extension;
95
96
97
98
99 public MavenArtifactLoader() {
100 this("jar");
101 }
102
103
104
105
106
107
108 public MavenArtifactLoader(String extension) {
109 this(extension, null);
110 }
111
112 static MavenArtifactLoader forTesting() {
113 return new MavenArtifactLoader("jar", knownRemoteRepos);
114 }
115
116 private MavenArtifactLoader(String extension, List<RemoteRepository> remoteRepositoriesForTesting) {
117 this.extension = requireNonNull(extension, "extension is null");
118
119 @SuppressWarnings("deprecation")
120 ServiceLocator serviceLocator = createServiceLocator();
121 this.repositorySystem = serviceLocator.getService(RepositorySystem.class);
122
123 try {
124 Settings settings = createSettings();
125 File localRepositoryLocation = settings.getLocalRepository() != null ? new File(settings.getLocalRepository()) : DEFAULT_USER_REPOSITORY;
126 LocalRepository localRepository = new LocalRepository(localRepositoryLocation);
127
128 if (remoteRepositoriesForTesting != null) {
129 this.remoteRepositories = remoteRepositoriesForTesting;
130 } else {
131 this.remoteRepositories = extractRemoteRepositories(settings);
132 }
133
134 DefaultRepositorySystemSession mavenSession = MavenRepositorySystemUtils.newSession();
135
136 this.mavenSession = mavenSession.setLocalRepositoryManager(repositorySystem.newLocalRepositoryManager(mavenSession, localRepository));
137
138 } catch (SettingsBuildingException e) {
139 throw new IllegalStateException("Could not load maven settings:", e);
140 }
141 }
142
143
144
145
146
147
148
149
150 public MavenVersionMatchBuilder builder(String groupId, String artifactId) {
151 requireNonNull(groupId, "groupId is null");
152 requireNonNull(artifactId, "artifactId is null");
153
154 return new MavenVersionMatchBuilder(this, groupId, artifactId);
155 }
156
157
158
159
160
161
162
163
164
165
166 public File getArtifactFile(String groupId, String artifactId, String version) throws IOException {
167 requireNonNull(groupId, "groupId is null");
168 requireNonNull(artifactId, "artifactId is null");
169 requireNonNull(version, "version is null");
170
171 ArtifactRequest artifactRequest = new ArtifactRequest();
172 artifactRequest.setArtifact(new DefaultArtifact(groupId, artifactId, extension, version));
173 artifactRequest.setRepositories(this.remoteRepositories);
174 try {
175 ArtifactResult artifactResult = this.repositorySystem.resolveArtifact(mavenSession, artifactRequest);
176 Artifact artifact = artifactResult.getArtifact();
177 return artifact.getFile();
178 } catch (RepositoryException e) {
179 throw new IOException(e);
180 }
181 }
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206 public String findLatestVersion(String groupId, String artifactId, String version) throws IOException {
207 requireNonNull(groupId, "groupId is null");
208 requireNonNull(artifactId, "artifactId is null");
209 requireNonNull(version, "version is null");
210 return builder(groupId, artifactId)
211 .partialMatch(version)
212 .extension(extension)
213 .includeSnapshots(true)
214 .findBestMatch()
215 .orElseThrow(() -> new IOException(format("No suitable candidate for %s:%s:%s found!", groupId, artifactId, version)));
216 }
217
218 SortedSet<Version> findAllVersions(MavenVersionMatchBuilder builder) throws IOException {
219
220 Artifact artifact = new DefaultArtifact(builder.groupId(), builder.artifactId(), builder.extension(), "[0,)");
221
222 VersionRangeRequest rangeRequest = new VersionRangeRequest();
223 rangeRequest.setArtifact(artifact);
224 rangeRequest.setRepositories(this.remoteRepositories);
225
226 try {
227 VersionRangeResult rangeResult = this.repositorySystem.resolveVersionRange(mavenSession, rangeRequest);
228 SortedSet<Version> resultBuilder = new TreeSet<>();
229 List<Version> artifactVersions = rangeResult.getVersions();
230 VersionStrategy versionStrategy = builder.versionStrategy();
231 if (artifactVersions != null) {
232 for (Version artifactVersion : artifactVersions) {
233 boolean isSnapshot = artifactVersion.toString().endsWith("-SNAPSHOT");
234 boolean match = versionStrategy.matchVersion(artifactVersion);
235
236
237 if (isSnapshot) {
238 match &= builder.includeSnapshots();
239 }
240
241 if (match) {
242 resultBuilder.add(artifactVersion);
243 }
244 }
245 }
246 return Collections.unmodifiableSortedSet(resultBuilder);
247 } catch (VersionRangeResolutionException e) {
248 throw new IOException(format("Could not resolve version range: %s", rangeRequest), e);
249 }
250 }
251
252
253 @SuppressWarnings("deprecation")
254 private static ServiceLocator createServiceLocator() {
255 DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
256
257 locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
258 locator.addService(TransporterFactory.class, FileTransporterFactory.class);
259 locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
260
261 locator.setErrorHandler(new DefaultServiceLocator.ErrorHandler() {
262 @Override
263 public void serviceCreationFailed(Class<?> type, Class<?> impl, Throwable e) {
264 LOG.error(format("Could not create instance of %s (implementation %s): ", type.getSimpleName(), impl.getSimpleName()), e);
265 }
266 });
267
268 return locator;
269 }
270
271 private static Settings createSettings() throws SettingsBuildingException {
272 SettingsBuildingRequest settingsBuildingRequest = new DefaultSettingsBuildingRequest()
273 .setSystemProperties(System.getProperties())
274 .setUserSettingsFile(DEFAULT_USER_SETTINGS_FILE)
275 .setGlobalSettingsFile(DEFAULT_GLOBAL_SETTINGS_FILE);
276
277 DefaultSettingsBuilderFactory settingBuilderFactory = new DefaultSettingsBuilderFactory();
278 DefaultSettingsBuilder settingsBuilder = settingBuilderFactory.newInstance();
279 SettingsBuildingResult settingsBuildingResult = settingsBuilder.build(settingsBuildingRequest);
280
281 return settingsBuildingResult.getEffectiveSettings();
282 }
283
284 private static List<RemoteRepository> extractRemoteRepositories(Settings settings) {
285 Map<String, Profile> profiles = settings.getProfilesAsMap();
286 List<RemoteRepository> builder = new ArrayList<>();
287
288 boolean foundRepository = false;
289 for (String profileName : settings.getActiveProfiles()) {
290 Profile profile = profiles.get(profileName);
291 if (profile != null) {
292 List<Repository> repositories = profile.getRepositories();
293 if (repositories != null) {
294 for (Repository repo : repositories) {
295 builder.add(new RemoteRepository.Builder(repo.getId(), "default", repo.getUrl()).build());
296 foundRepository = true;
297 }
298 }
299 }
300 }
301
302 if (!foundRepository && !settings.isOffline()) {
303 builder.add(CENTRAL_REPO);
304 }
305
306 return Collections.unmodifiableList(builder);
307 }
308 }