Skip to content

Commit 1a97fd6

Browse files
committed
Add v3 ToolchainFactory bridge for plugin injection
This change fixes the issue where v3 API plugins cannot inject v3 API beans when only v4 API implementations are available in the container. The problem occurs because: - Maven 4 provides only v4 API implementations (e.g., ToolchainFactory) - V3 plugins using @Inject with @nAmed qualifiers expect v3 API types - Without a bridge, DI fails to find matching v3 API beans Solution: - Add @provides method in ToolchainManagerFactory to create v3 bridges - Bridge wraps v4 ToolchainFactory and exposes it as v3 ToolchainFactory - Uses @nAmed(\jdk\) qualifier to match plugin injection requirements - Handles API conversion between v3 and v4 toolchain models This enables v3 plugins like maven-toolchains-plugin to successfully inject ToolchainFactory instances. Fixes apache/maven-toolchains-plugin#128
1 parent ec8d98c commit 1a97fd6

File tree

6 files changed

+302
-3
lines changed

6 files changed

+302
-3
lines changed

compat/maven-compat/src/main/java/org/apache/maven/toolchain/ToolchainManagerFactory.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,51 @@ DefaultToolchainManagerV4 v4Manager() {
7272
return new DefaultToolchainManagerV4();
7373
}
7474

75+
/**
76+
* Provides a v3 ToolchainFactory bridge for the "jdk" toolchain type.
77+
* This wraps the v4 ToolchainFactory implementation and exposes it as a v3 ToolchainFactory.
78+
*/
79+
@Provides
80+
@Named("jdk")
81+
@Typed(ToolchainFactory.class)
82+
ToolchainFactory jdkToolchainFactoryBridge() {
83+
return createV3FactoryBridge("jdk");
84+
}
85+
86+
/**
87+
* Creates a v3 ToolchainFactory bridge that wraps a v4 ToolchainFactory.
88+
*/
89+
private ToolchainFactory createV3FactoryBridge(String type) {
90+
org.apache.maven.api.services.ToolchainFactory v4Factory =
91+
lookup.lookup(org.apache.maven.api.services.ToolchainFactory.class, type);
92+
if (v4Factory == null) {
93+
return null;
94+
}
95+
return new ToolchainFactory() {
96+
@Override
97+
public ToolchainPrivate createToolchain(ToolchainModel model) throws MisconfiguredToolchainException {
98+
try {
99+
org.apache.maven.api.Toolchain v4Toolchain = v4Factory.createToolchain(model.getDelegate());
100+
return getToolchainV3(v4Toolchain);
101+
} catch (ToolchainFactoryException e) {
102+
throw new MisconfiguredToolchainException(e.getMessage(), e);
103+
}
104+
}
105+
106+
@Override
107+
public ToolchainPrivate createDefaultToolchain() {
108+
try {
109+
return v4Factory
110+
.createDefaultToolchain()
111+
.map(ToolchainManagerFactory.this::getToolchainV3)
112+
.orElse(null);
113+
} catch (ToolchainFactoryException e) {
114+
return null;
115+
}
116+
}
117+
};
118+
}
119+
75120
private org.apache.maven.impl.DefaultToolchainManager getDelegate() {
76121
return getToolchainManager(lookup, logger);
77122
}

impl/maven-core/src/main/java/org/apache/maven/internal/impl/SisuDiBridgeModule.java

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public class SisuDiBridgeModule extends AbstractModule {
6060

6161
protected final boolean discover;
6262
protected InjectorImpl injector;
63+
protected Provider<BeanLocator> parentLocator;
6364

6465
public SisuDiBridgeModule() {
6566
this(true);
@@ -73,7 +74,10 @@ public SisuDiBridgeModule(boolean discover) {
7374
protected void configure() {
7475
Provider<PlexusContainer> containerProvider = getProvider(PlexusContainer.class);
7576
Provider<BeanLocator> beanLocatorProvider = getProvider(BeanLocator.class);
76-
injector = new BridgeInjectorImpl(beanLocatorProvider, binder());
77+
// Store the parent container's BeanLocator for later use when looking up beans
78+
// that should come from the parent container, not the plugin realm
79+
this.parentLocator = beanLocatorProvider;
80+
injector = new BridgeInjectorImpl(beanLocatorProvider, binder(), parentLocator);
7781
bindScope(injector, containerProvider, SessionScoped.class, SessionScope.class);
7882
bindScope(injector, containerProvider, MojoExecutionScoped.class, MojoExecutionScope.class);
7983
injector.bindInstance(Injector.class, injector);
@@ -104,11 +108,17 @@ private void bindScope(
104108

105109
static class BridgeInjectorImpl extends InjectorImpl {
106110
final Provider<BeanLocator> locator;
111+
final Provider<BeanLocator> parentLocator;
107112
final Binder binder;
108113

109114
BridgeInjectorImpl(Provider<BeanLocator> locator, Binder binder) {
115+
this(locator, binder, locator);
116+
}
117+
118+
BridgeInjectorImpl(Provider<BeanLocator> locator, Binder binder, Provider<BeanLocator> parentLocator) {
110119
this.locator = locator;
111120
this.binder = binder;
121+
this.parentLocator = parentLocator;
112122
}
113123

114124
@Override
@@ -180,12 +190,22 @@ private <Q> Supplier<Q> getBeanSupplier(Dependency<Q> dep, Key<Q> key) {
180190
List<Binding<?>> list = new ArrayList<>();
181191
// Add DI bindings
182192
list.addAll(getBindings().getOrDefault(key, Set.of()));
183-
// Add Plexus bindings
193+
// Add Plexus bindings from the plugin realm
184194
for (var bean : locator.get().locate(toGuiceKey(key))) {
185195
if (isPlexusBean(bean)) {
186196
list.add(new BindingToBeanEntry<>(key).toBeanEntry(bean).prioritize(bean.getRank()));
187197
}
188198
}
199+
// If not found in plugin realm, try the parent container's BeanLocator
200+
// This is important for beans that should come from the parent container,
201+
// such as the Injector or other Maven services
202+
if (list.isEmpty() && parentLocator != locator) {
203+
for (var bean : parentLocator.get().locate(toGuiceKey(key))) {
204+
if (isPlexusBean(bean)) {
205+
list.add(new BindingToBeanEntry<>(key).toBeanEntry(bean).prioritize(bean.getRank()));
206+
}
207+
}
208+
}
189209
if (!list.isEmpty()) {
190210
list.sort(getPriorityComparator());
191211
//noinspection unchecked
@@ -226,12 +246,20 @@ private <Q> Supplier<Q> getListSupplier(Key<Q> key) {
226246
List<Binding<?>> list = new ArrayList<>();
227247
// Add DI bindings
228248
list.addAll(getBindings().getOrDefault(elementType, Set.of()));
229-
// Add Plexus bindings
249+
// Add Plexus bindings from the plugin realm
230250
for (var bean : locator.get().locate(toGuiceKey(elementType))) {
231251
if (isPlexusBean(bean)) {
232252
list.add(new BindingToBeanEntry<>(elementType).toBeanEntry(bean));
233253
}
234254
}
255+
// If not found in plugin realm, try the parent container's BeanLocator
256+
if (list.isEmpty() && parentLocator != locator) {
257+
for (var bean : parentLocator.get().locate(toGuiceKey(elementType))) {
258+
if (isPlexusBean(bean)) {
259+
list.add(new BindingToBeanEntry<>(elementType).toBeanEntry(bean));
260+
}
261+
}
262+
}
235263
//noinspection unchecked
236264
return (Q) list(list.stream().sorted(getPriorityComparator()).toList(), this::getInstance);
237265
};
@@ -262,6 +290,18 @@ private <Q> Supplier<Q> getMapSupplier(Key<Q> key) {
262290
map.compute(name, (n, ob) -> ob == null || ob.getPriority() < b.getPriority() ? b : ob);
263291
}
264292
}
293+
// If not found in plugin realm, try the parent container's BeanLocator
294+
if (map.isEmpty() && parentLocator != locator) {
295+
for (var bean : parentLocator.get().locate(toGuiceKey(valueType))) {
296+
if (isPlexusBean(bean)) {
297+
Binding<?> b = new BindingToBeanEntry<>(valueType)
298+
.toBeanEntry(bean)
299+
.prioritize(bean.getRank());
300+
String name = bean.getKey() instanceof com.google.inject.name.Named n ? n.value() : "";
301+
map.compute(name, (n, ob) -> ob == null || ob.getPriority() < b.getPriority() ? b : ob);
302+
}
303+
}
304+
}
265305
//noinspection unchecked
266306
return (Q) map(map, this::getInstance);
267307
};
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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.it;
20+
21+
import java.io.File;
22+
23+
import org.junit.jupiter.api.Test;
24+
25+
/**
26+
* This is a test set for <a href="https://github.com/apache/maven/issues/11314">GH-11314</a>.
27+
*
28+
* Verifies that V3 Mojos can be injected with v3 API beans that are bridged from v4 API
29+
* implementations. Specifically tests the case where a plugin needs to inject ToolchainFactory
30+
* with a named qualifier.
31+
*
32+
* @see <a href="https://github.com/apache/maven-toolchains-plugin/issues/128">maven-toolchains-plugin#128</a>
33+
*/
34+
public class MavenITgh11314PluginInjectionTest extends AbstractMavenIntegrationTestCase {
35+
36+
/**
37+
* Verify that V3 Mojos can be injected with v3 ToolchainFactory which is bridged from
38+
* the v4 ToolchainFactory implementation. This test reproduces the issue where a plugin
39+
* with a field requiring injection of ToolchainFactory with @Named("jdk") fails with
40+
* NullInjectedIntoNonNullable error.
41+
*
42+
* @throws Exception in case of failure
43+
*/
44+
@Test
45+
public void testV3MojoWithMavenContainerInjection() throws Exception {
46+
File testDir = extractResources("/gh-11314-v3-mojo-injection");
47+
48+
// First, build and install the test plugin
49+
File pluginDir = new File(testDir, "plugin");
50+
Verifier pluginVerifier = newVerifier(pluginDir.getAbsolutePath());
51+
pluginVerifier.addCliArgument("install");
52+
pluginVerifier.execute();
53+
pluginVerifier.verifyErrorFreeLog();
54+
55+
// Now run the test project that uses the plugin
56+
Verifier verifier = newVerifier(testDir.getAbsolutePath());
57+
verifier.setAutoclean(false);
58+
verifier.deleteDirectory("target");
59+
verifier.addCliArgument("validate");
60+
verifier.execute();
61+
verifier.verifyErrorFreeLog();
62+
}
63+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<groupId>org.apache.maven.its.gh11314</groupId>
5+
<artifactId>test-plugin</artifactId>
6+
<version>0.0.1-SNAPSHOT</version>
7+
<packaging>maven-plugin</packaging>
8+
9+
<properties>
10+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
11+
<maven.version>4.0.0-rc-4</maven.version>
12+
<maven.compiler.source>17</maven.compiler.source>
13+
<maven.compiler.target>17</maven.compiler.target>
14+
</properties>
15+
16+
<dependencies>
17+
<dependency>
18+
<groupId>org.apache.maven</groupId>
19+
<artifactId>maven-plugin-api</artifactId>
20+
<version>${maven.version}</version>
21+
<scope>provided</scope>
22+
</dependency>
23+
<dependency>
24+
<groupId>org.apache.maven</groupId>
25+
<artifactId>maven-core</artifactId>
26+
<version>${maven.version}</version>
27+
<scope>provided</scope>
28+
</dependency>
29+
<dependency>
30+
<groupId>org.apache.maven</groupId>
31+
<artifactId>maven-compat</artifactId>
32+
<version>${maven.version}</version>
33+
<scope>provided</scope>
34+
</dependency>
35+
<dependency>
36+
<groupId>org.apache.maven.plugin-tools</groupId>
37+
<artifactId>maven-plugin-annotations</artifactId>
38+
<version>3.11.0</version>
39+
<scope>provided</scope>
40+
</dependency>
41+
<dependency>
42+
<groupId>javax.inject</groupId>
43+
<artifactId>javax.inject</artifactId>
44+
<version>1</version>
45+
<scope>provided</scope>
46+
</dependency>
47+
</dependencies>
48+
49+
<build>
50+
<plugins>
51+
<plugin>
52+
<groupId>org.apache.maven.plugins</groupId>
53+
<artifactId>maven-plugin-plugin</artifactId>
54+
<version>3.11.0</version>
55+
<executions>
56+
<execution>
57+
<id>default-descriptor</id>
58+
<goals>
59+
<goal>descriptor</goal>
60+
</goals>
61+
<configuration>
62+
<goalPrefix>plugin</goalPrefix>
63+
</configuration>
64+
</execution>
65+
</executions>
66+
</plugin>
67+
</plugins>
68+
</build>
69+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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.its.gh11314;
20+
21+
import javax.inject.Inject;
22+
import javax.inject.Named;
23+
24+
import org.apache.maven.plugin.AbstractMojo;
25+
import org.apache.maven.plugin.MojoExecutionException;
26+
import org.apache.maven.plugins.annotations.Mojo;
27+
import org.apache.maven.toolchain.ToolchainFactory;
28+
29+
/**
30+
* A test Mojo that requires injection of ToolchainFactory from the Maven container.
31+
* This reproduces the issue where V3 Mojos cannot be injected with v3 API beans
32+
* when only v4 API implementations are available.
33+
*/
34+
@Mojo(name = "test-goal")
35+
public class TestMojo extends AbstractMojo {
36+
37+
/**
38+
* The ToolchainFactory from the Maven container.
39+
* This field requires injection of the v3 API ToolchainFactory with "jdk" hint.
40+
*/
41+
@Inject
42+
@Named("jdk")
43+
private ToolchainFactory factory;
44+
45+
@Override
46+
public void execute() throws MojoExecutionException {
47+
if (factory == null) {
48+
throw new MojoExecutionException("ToolchainFactory was not injected!");
49+
}
50+
getLog().info("ToolchainFactory successfully injected: "
51+
+ factory.getClass().getName());
52+
}
53+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<groupId>org.apache.maven.its.gh11314</groupId>
5+
<artifactId>test-project</artifactId>
6+
<version>0.0.1-SNAPSHOT</version>
7+
<packaging>pom</packaging>
8+
9+
<properties>
10+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
11+
</properties>
12+
13+
<build>
14+
<plugins>
15+
<plugin>
16+
<groupId>org.apache.maven.its.gh11314</groupId>
17+
<artifactId>test-plugin</artifactId>
18+
<version>0.0.1-SNAPSHOT</version>
19+
<executions>
20+
<execution>
21+
<goals>
22+
<goal>test-goal</goal>
23+
</goals>
24+
</execution>
25+
</executions>
26+
</plugin>
27+
</plugins>
28+
</build>
29+
</project>

0 commit comments

Comments
 (0)