Skip to content

Commit addaa7b

Browse files
Fixed issues with missing classes when running maven plugin
Signed-off-by: Marcin Grzejszczak <[email protected]>
1 parent b74920a commit addaa7b

File tree

4 files changed

+150
-38
lines changed

4 files changed

+150
-38
lines changed

spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/AetherFactories.java

Lines changed: 138 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,29 @@
3434
import org.eclipse.aether.DefaultRepositorySystemSession;
3535
import org.eclipse.aether.RepositorySystem;
3636
import org.eclipse.aether.RepositorySystemSession;
37-
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
3837
import org.eclipse.aether.impl.DefaultServiceLocator;
3938
import org.eclipse.aether.repository.LocalRepository;
4039
import org.eclipse.aether.repository.RepositoryPolicy;
4140
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
4241
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
43-
import org.eclipse.aether.transport.file.FileTransporterFactory;
44-
import org.eclipse.aether.transport.http.HttpTransporterFactory;
4542

43+
import org.springframework.lang.Nullable;
4644
import org.springframework.util.StringUtils;
4745

46+
/**
47+
* Utilities for creating/obtaining Aether (Maven Resolver) components.
48+
*
49+
* <p>
50+
* <b>Key changes vs the old version:</b>
51+
* <ul>
52+
* <li>No hard reference to {@code BasicRepositoryConnectorFactory} or transporter
53+
* implementations. We register them reflectively if present, otherwise we rely on Maven's
54+
* injected components or whatever the runtime provides via ServiceLoader.</li>
55+
* <li>Supports using Maven-injected {@link RepositorySystem} and
56+
* {@link RepositorySystemSession} when called from a Mojo, avoiding classpath issues
57+
* entirely.</li>
58+
* </ul>
59+
*/
4860
final class AetherFactories {
4961

5062
private static final Log log = LogFactory.getLog(AetherFactories.class);
@@ -61,21 +73,126 @@ private AetherFactories() {
6173
throw new IllegalStateException("Can't instantiate a utility class");
6274
}
6375

64-
public static RepositorySystem newRepositorySystem() {
76+
/**
77+
* Return the injected system if available, otherwise create a new one via a
78+
* {@link DefaultServiceLocator} without hard-linking to optional providers.
79+
*/
80+
static RepositorySystem repositorySystemOr(@Nullable RepositorySystem injectedOrNull) {
81+
if (injectedOrNull != null) {
82+
if (log.isDebugEnabled()) {
83+
log.debug("Using Maven-injected RepositorySystem");
84+
}
85+
return injectedOrNull;
86+
}
87+
if (log.isDebugEnabled()) {
88+
log.debug("No injected RepositorySystem provided; creating one via ServiceLocator");
89+
}
90+
return newRepositorySystemFallback();
91+
}
92+
93+
/**
94+
* Return the injected session if available, otherwise create a new session for the
95+
* given system.
96+
*/
97+
static RepositorySystemSession sessionOr(RepositorySystem system, @Nullable RepositorySystemSession injectedOrNull,
98+
boolean workOffline) {
99+
if (injectedOrNull != null) {
100+
if (log.isDebugEnabled()) {
101+
log.debug("Using Maven-injected RepositorySystemSession (workOffline=" + injectedOrNull.isOffline()
102+
+ ")");
103+
}
104+
return injectedOrNull;
105+
}
106+
return newSession(system, workOffline);
107+
}
108+
109+
/**
110+
* Fallback creation using a ServiceLocator. This tries to register common providers
111+
* reflectively:
112+
* <ul>
113+
* <li>org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory</li>
114+
* <li>org.eclipse.aether.transport.file.FileTransporterFactory</li>
115+
* <li>org.eclipse.aether.transport.http.HttpTransporterFactory</li>
116+
* </ul>
117+
* If any of these are missing on the classpath, we simply don't register them and let
118+
* ServiceLoader discover whatever is available. This avoids
119+
* {@code NoClassDefFoundError} at class load time.
120+
*/
121+
private static RepositorySystem newRepositorySystemFallback() {
65122
DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
66-
locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
67-
locator.addService(TransporterFactory.class, FileTransporterFactory.class);
68-
locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
69-
return locator.getService(RepositorySystem.class);
123+
124+
// Helpful diagnostics
125+
locator.setErrorHandler(new DefaultServiceLocator.ErrorHandler() {
126+
@Override
127+
public void serviceCreationFailed(Class<?> type, Class<?> impl, Throwable exception) {
128+
if (log.isDebugEnabled()) {
129+
log.debug("Failed to create service " + type.getName() + " via "
130+
+ (impl != null ? impl.getName() : "<null>") + ": " + exception.toString());
131+
}
132+
}
133+
});
134+
135+
// Try to register connector + transporters reflectively, but do not hard-link
136+
// them.
137+
registerIfPresent(locator, "org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory",
138+
RepositoryConnectorFactory.class);
139+
140+
registerIfPresent(locator, "org.eclipse.aether.transport.file.FileTransporterFactory",
141+
TransporterFactory.class);
142+
143+
registerIfPresent(locator, "org.eclipse.aether.transport.http.HttpTransporterFanewRepositorySystemctory",
144+
TransporterFactory.class);
145+
146+
RepositorySystem system = locator.getService(RepositorySystem.class);
147+
148+
// If system still ended up null, give a helpful hint.
149+
if (system == null) {
150+
throw new IllegalStateException("Failed to obtain RepositorySystem. "
151+
+ "Ensure Maven Resolver is on the classpath and, when running inside a Maven plugin, "
152+
+ "prefer using the Maven-injected RepositorySystem/RepositorySystemSession.");
153+
}
154+
return system;
70155
}
71156

72-
public static RepositorySystemSession newSession(RepositorySystem system, boolean workOffline) {
157+
@SuppressWarnings({ "unchecked", "rawtypes" })
158+
private static void registerIfPresent(DefaultServiceLocator locator, String implClassName, Class<?> serviceType) {
159+
try {
160+
ClassLoader cl = Thread.currentThread().getContextClassLoader();
161+
if (cl == null) {
162+
cl = AetherFactories.class.getClassLoader();
163+
}
164+
Class<?> impl = Class.forName(implClassName, false, cl);
165+
if (!serviceType.isAssignableFrom(impl)) {
166+
if (log.isDebugEnabled()) {
167+
log.debug("Class " + implClassName + " is not assignable to " + serviceType.getName());
168+
}
169+
return;
170+
}
171+
locator.addService((Class) serviceType, (Class) impl);
172+
if (log.isDebugEnabled()) {
173+
log.debug("Registered " + implClassName + " as " + serviceType.getSimpleName());
174+
}
175+
}
176+
catch (ClassNotFoundException ex) {
177+
// Silently skip; not on classpath (e.g., plugin didn't ship connector-basic).
178+
if (log.isDebugEnabled()) {
179+
log.debug("Optional provider not found on classpath: " + implClassName);
180+
}
181+
}
182+
}
183+
184+
/**
185+
* Create a new {@link RepositorySystemSession}, controlling offline/update/checksum
186+
* policies.
187+
*/
188+
static RepositorySystemSession newSession(RepositorySystem system, boolean workOffline) {
73189
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
74190
session.setOffline(workOffline);
75191
if (!workOffline) {
76192
session.setUpdatePolicy(RepositoryPolicy.UPDATE_POLICY_ALWAYS);
77193
}
78194
session.setChecksumPolicy(RepositoryPolicy.CHECKSUM_POLICY_WARN);
195+
79196
String localRepositoryDirectory = localRepositoryDirectory(workOffline);
80197
if (log.isDebugEnabled()) {
81198
log.debug("Local Repository Directory set to [" + localRepositoryDirectory + "]. Work offline: ["
@@ -86,7 +203,11 @@ public static RepositorySystemSession newSession(RepositorySystem system, boolea
86203
return session;
87204
}
88205

89-
protected static String localRepositoryDirectory(boolean workOffline) {
206+
/**
207+
* Determine local repo directory: respect settings/system prop; use temp when online
208+
* to avoid pollution.
209+
*/
210+
static String localRepositoryDirectory(boolean workOffline) {
90211
String localRepoLocationFromSettings = settings().getLocalRepository();
91212
String currentLocalRepo = readPropertyFromSystemProps(localRepoLocationFromSettings);
92213
if (workOffline) {
@@ -101,17 +222,17 @@ private static String temporaryDirectory() {
101222
}
102223
catch (IOException e) {
103224
if (log.isDebugEnabled()) {
104-
log.debug("Failed to create a new temporary directory, will generate a new one under temp dir");
225+
log.debug("Failed to create a new temporary directory, will generate a new one under temp dir", e);
105226
}
106227
return System.getProperty("java.io.tmpdir") + File.separator + RANDOM.nextInt();
107228
}
108229
}
109230

110-
private static String readPropertyFromSystemProps(String localRepoLocationFromSettings) {
231+
private static String readPropertyFromSystemProps(@Nullable String localRepoLocationFromSettings) {
111232
String mavenLocalRepo = fromSystemPropOrEnv(MAVEN_LOCAL_REPOSITORY_LOCATION);
112233
return StringUtils.hasText(mavenLocalRepo) ? mavenLocalRepo
113-
: localRepoLocationFromSettings != null ? localRepoLocationFromSettings
114-
: System.getProperty("user.home") + File.separator + ".m2" + File.separator + "repository";
234+
: (localRepoLocationFromSettings != null ? localRepoLocationFromSettings
235+
: System.getProperty("user.home") + File.separator + ".m2" + File.separator + "repository");
115236
}
116237

117238
// system prop takes precedence over env var
@@ -139,22 +260,21 @@ private static File userSettings() {
139260
return new File(user);
140261
}
141262

142-
protected static Settings settings() {
263+
static Settings settings() {
143264
SettingsBuilder builder = new DefaultSettingsBuilderFactory().newInstance();
144265
SettingsBuildingRequest request = new DefaultSettingsBuildingRequest();
145266
request.setUserSettingsFile(userSettings());
146267
String global = fromSystemPropOrEnv(MAVEN_GLOBAL_SETTINGS_LOCATION);
147268
if (global != null) {
148269
request.setGlobalSettingsFile(new File(global));
149270
}
150-
SettingsBuildingResult result;
151271
try {
152-
result = builder.build(request);
272+
SettingsBuildingResult result = builder.build(request);
273+
return result.getEffectiveSettings();
153274
}
154275
catch (SettingsBuildingException ex) {
155276
throw new IllegalStateException(ex);
156277
}
157-
return result.getEffectiveSettings();
158278
}
159279

160280
}

spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/AetherStubDownloader.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
4848
import org.springframework.util.StringUtils;
4949

50-
import static org.springframework.cloud.contract.stubrunner.AetherFactories.newRepositorySystem;
5150
import static org.springframework.cloud.contract.stubrunner.AetherFactories.newSession;
5251
import static org.springframework.cloud.contract.stubrunner.AetherFactories.settings;
5352
import static org.springframework.cloud.contract.stubrunner.util.ZipCategory.unzipTo;
@@ -106,7 +105,7 @@ public AetherStubDownloader(StubRunnerOptions stubRunnerOptions) {
106105
throw new UnsupportedOperationException(
107106
"You can't use Aether downloader when you use classpath to find stubs");
108107
}
109-
this.repositorySystem = newRepositorySystem();
108+
this.repositorySystem = AetherFactories.repositorySystemOr(null);
110109
this.workOffline = stubRunnerOptions.stubsMode == StubRunnerProperties.StubsMode.LOCAL;
111110
this.session = newSession(this.repositorySystem, this.workOffline);
112111
registerShutdownHook();

spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/AetherStubDownloaderSpec.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class AetherStubDownloaderSpec extends Specification {
8080
ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + m2repoFolder).getAbsolutePath() + '</localRepository></settings>'
8181
System.setProperty("org.apache.maven.user-settings", tempSettings.getAbsolutePath())
8282
RepositorySystemSession repositorySystemSession =
83-
AetherFactories.newSession(AetherFactories.newRepositorySystem(), true)
83+
AetherFactories.newSession(AetherFactories.repositorySystemOr(null), true)
8484

8585
and:
8686
StubRunnerOptions stubRunnerOptions = new StubRunnerOptionsBuilder()

spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/pom.xml

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
</parent>
1313

1414
<prerequisites>
15-
<maven>3.6</maven>
15+
<maven>3.9</maven>
1616
</prerequisites>
1717

1818
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
@@ -41,6 +41,7 @@
4141
<maven-plugin-testing-harness.version>4.0.0-beta-4</maven-plugin-testing-harness.version>
4242
<maven-shared-incremental.version>1.1</maven-shared-incremental.version>
4343
<slf4j-api.version>2.0.7</slf4j-api.version>
44+
<plexus-utils.version>3.6.0</plexus-utils.version> <!-- If not set the provided version is 3.0.x and MethodMissing is thrown -->
4445
</properties>
4546

4647
<inceptionYear>2016</inceptionYear>
@@ -293,27 +294,22 @@
293294
<dependency>
294295
<groupId>org.apache.maven.resolver</groupId>
295296
<artifactId>maven-resolver-api</artifactId>
296-
<scope>provided</scope>
297297
</dependency>
298298
<dependency>
299299
<groupId>org.apache.maven.resolver</groupId>
300300
<artifactId>maven-resolver-impl</artifactId>
301-
<scope>provided</scope>
302301
</dependency>
303302
<dependency>
304303
<groupId>org.apache.maven.resolver</groupId>
305304
<artifactId>maven-resolver-transport-file</artifactId>
306-
<scope>provided</scope>
307305
</dependency>
308306
<dependency>
309307
<groupId>org.apache.maven.resolver</groupId>
310308
<artifactId>maven-resolver-transport-http</artifactId>
311-
<scope>provided</scope>
312309
</dependency>
313310
<dependency>
314311
<groupId>org.apache.maven.resolver</groupId>
315312
<artifactId>maven-resolver-connector-basic</artifactId>
316-
<scope>provided</scope>
317313
</dependency>
318314
<dependency>
319315
<groupId>org.apache.maven</groupId>
@@ -333,26 +329,18 @@
333329
<dependency>
334330
<groupId>org.codehaus.plexus</groupId>
335331
<artifactId>plexus-utils</artifactId>
336-
<version>4.0.2</version>
332+
<version>${plexus-utils.version}</version>
337333
</dependency>
338-
339334
<dependency>
340335
<groupId>ch.qos.logback</groupId>
341336
<artifactId>logback-core</artifactId>
342337
</dependency>
343-
<dependency>
344-
<groupId>org.slf4j</groupId>
345-
<artifactId>slf4j-simple</artifactId>
346-
<scope>test</scope>
347-
</dependency>
348-
349338
<dependency>
350339
<groupId>org.apache.maven</groupId>
351340
<artifactId>maven-core</artifactId>
352341
<version>${maven.version}</version>
353342
<scope>provided</scope>
354343
</dependency>
355-
356344
<dependency>
357345
<groupId>org.apache.maven</groupId>
358346
<artifactId>maven-plugin-api</artifactId>
@@ -377,13 +365,12 @@
377365
<groupId>org.apache.maven.plugin-tools</groupId>
378366
<artifactId>maven-plugin-annotations</artifactId>
379367
<version>${maven-plugin-annotations.version}</version>
380-
<scope>provided</scope>
381368
</dependency>
382-
383369
<dependency>
384370
<groupId>org.apache.maven</groupId>
385371
<artifactId>maven-archiver</artifactId>
386372
<version>${maven-archiver.version}</version>
373+
<scope>provided</scope>
387374
</dependency>
388375
<dependency>
389376
<groupId>org.codehaus.plexus</groupId>
@@ -396,12 +383,18 @@
396383
<version>${maven-shared-incremental.version}</version>
397384
</dependency>
398385

386+
<dependency>
387+
<groupId>org.slf4j</groupId>
388+
<artifactId>slf4j-simple</artifactId>
389+
<scope>test</scope>
390+
</dependency>
399391
<dependency>
400392
<groupId>org.apache.maven</groupId>
401393
<artifactId>maven-compat</artifactId>
402394
<version>${maven.version}</version>
403395
<scope>test</scope>
404396
</dependency>
397+
<!-- TODO: Migrate to junit5 for maven tests -->
405398
<dependency>
406399
<groupId>junit</groupId>
407400
<artifactId>junit</artifactId>

0 commit comments

Comments
 (0)