1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.cling.invoker;
20
21 import java.io.File;
22 import java.nio.file.Path;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Set;
28 import java.util.function.UnaryOperator;
29
30 import com.google.inject.AbstractModule;
31 import com.google.inject.Module;
32 import org.apache.maven.api.Constants;
33 import org.apache.maven.api.ProtoSession;
34 import org.apache.maven.api.cli.Logger;
35 import org.apache.maven.api.cli.extensions.CoreExtension;
36 import org.apache.maven.api.services.MessageBuilderFactory;
37 import org.apache.maven.api.services.SettingsBuilder;
38 import org.apache.maven.cling.extensions.BootstrapCoreExtensionManager;
39 import org.apache.maven.cling.extensions.ExtensionConfigurationModule;
40 import org.apache.maven.cling.extensions.LoadedCoreExtension;
41 import org.apache.maven.cling.logging.Slf4jLoggerManager;
42 import org.apache.maven.di.Injector;
43 import org.apache.maven.execution.DefaultMavenExecutionRequest;
44 import org.apache.maven.execution.MavenExecutionRequest;
45 import org.apache.maven.execution.MavenExecutionRequestPopulator;
46 import org.apache.maven.execution.scope.internal.MojoExecutionScope;
47 import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule;
48 import org.apache.maven.extension.internal.CoreExports;
49 import org.apache.maven.extension.internal.CoreExtensionEntry;
50 import org.apache.maven.internal.impl.DefaultLookup;
51 import org.apache.maven.session.scope.internal.SessionScope;
52 import org.apache.maven.session.scope.internal.SessionScopeModule;
53 import org.codehaus.plexus.ContainerConfiguration;
54 import org.codehaus.plexus.DefaultContainerConfiguration;
55 import org.codehaus.plexus.DefaultPlexusContainer;
56 import org.codehaus.plexus.PlexusConstants;
57 import org.codehaus.plexus.PlexusContainer;
58 import org.codehaus.plexus.classworlds.ClassWorld;
59 import org.codehaus.plexus.classworlds.realm.ClassRealm;
60 import org.codehaus.plexus.logging.LoggerManager;
61 import org.slf4j.ILoggerFactory;
62
63 import static java.util.Objects.requireNonNull;
64 import static org.apache.maven.cling.invoker.CliUtils.toPlexusLoggingLevel;
65
66
67
68
69
70
71 public class PlexusContainerCapsuleFactory<C extends LookupContext> implements ContainerCapsuleFactory<C> {
72 @Override
73 public ContainerCapsule createContainerCapsule(
74 LookupInvoker<C> invoker, C context, CoreExtensionSelector<C> coreExtensionSelector) throws Exception {
75 requireNonNull(invoker, "invoker");
76 requireNonNull(context, "context");
77 requireNonNull(coreExtensionSelector, "coreExtensionSelector");
78 return new PlexusContainerCapsule(
79 context,
80 Thread.currentThread().getContextClassLoader(),
81 container(invoker, context, coreExtensionSelector));
82 }
83
84 protected DefaultPlexusContainer container(
85 LookupInvoker<C> invoker, C context, CoreExtensionSelector<C> coreExtensionSelector) throws Exception {
86 ClassWorld classWorld = requireNonNull(invoker.protoLookup.lookup(ClassWorld.class), "classWorld");
87 ClassRealm coreRealm = classWorld.getClassRealm("plexus.core");
88 List<Path> extClassPath = parseExtClasspath(context);
89 CoreExtensionEntry coreEntry = CoreExtensionEntry.discoverFrom(coreRealm);
90 List<LoadedCoreExtension> loadedExtensions = loadCoreExtensions(
91 invoker,
92 context,
93 coreRealm,
94 coreEntry.getExportedArtifacts(),
95 coreExtensionSelector.selectCoreExtensions(invoker, context));
96 List<CoreExtensionEntry> loadedExtensionsEntries =
97 loadedExtensions.stream().map(LoadedCoreExtension::entry).toList();
98 ClassRealm containerRealm =
99 setupContainerRealm(context.logger, classWorld, coreRealm, extClassPath, loadedExtensionsEntries);
100 ContainerConfiguration cc = new DefaultContainerConfiguration()
101 .setClassWorld(classWorld)
102 .setRealm(containerRealm)
103 .setClassPathScanning(PlexusConstants.SCANNING_INDEX)
104 .setAutoWiring(true)
105 .setJSR250Lifecycle(true)
106 .setStrictClassPathScanning(false)
107 .setName("maven");
108 customizeContainerConfiguration(context, cc);
109
110 CoreExports exports = new CoreExports(
111 containerRealm,
112 collectExportedArtifacts(coreEntry, loadedExtensionsEntries),
113 collectExportedPackages(coreEntry, loadedExtensionsEntries));
114 Thread.currentThread().setContextClassLoader(containerRealm);
115 DefaultPlexusContainer container = new DefaultPlexusContainer(cc, getCustomModule(context, exports));
116
117
118 container.setLookupRealm(null);
119 Thread.currentThread().setContextClassLoader(container.getContainerRealm());
120
121 container.setLoggerManager(createLoggerManager());
122 ProtoSession protoSession = context.protoSession;
123 UnaryOperator<String> extensionSource = expression -> {
124 String value = protoSession.getUserProperties().get(expression);
125 if (value == null) {
126 value = protoSession.getSystemProperties().get(expression);
127 }
128 return value;
129 };
130 List<Throwable> failures = new ArrayList<>();
131 for (LoadedCoreExtension extension : loadedExtensions) {
132 container.discoverComponents(
133 extension.entry().getClassRealm(),
134 new AbstractModule() {
135 @Override
136 protected void configure() {
137 try {
138 container
139 .lookup(Injector.class)
140 .discover(extension.entry().getClassRealm());
141 } catch (Throwable e) {
142 failures.add(new IllegalStateException(
143 "Injection failure in "
144 + extension.coreExtension().getId(),
145 e));
146 }
147 }
148 },
149 new SessionScopeModule(container.lookup(SessionScope.class)),
150 new MojoExecutionScopeModule(container.lookup(MojoExecutionScope.class)),
151 new ExtensionConfigurationModule(extension.entry(), extensionSource));
152 }
153 if (!failures.isEmpty()) {
154 IllegalStateException mavenDiFailed = new IllegalStateException(
155 "Maven dependency injection failed for at least one of the registered core extension");
156 failures.forEach(mavenDiFailed::addSuppressed);
157 throw mavenDiFailed;
158 }
159 container.getLoggerManager().setThresholds(toPlexusLoggingLevel(context.loggerLevel));
160 customizeContainer(context, container);
161
162 return container;
163 }
164
165 protected Set<String> collectExportedArtifacts(
166 CoreExtensionEntry coreEntry, List<CoreExtensionEntry> extensionEntries) {
167 Set<String> exportedArtifacts = new HashSet<>(coreEntry.getExportedArtifacts());
168 for (CoreExtensionEntry extension : extensionEntries) {
169 exportedArtifacts.addAll(extension.getExportedArtifacts());
170 }
171 return exportedArtifacts;
172 }
173
174 protected Set<String> collectExportedPackages(
175 CoreExtensionEntry coreEntry, List<CoreExtensionEntry> extensionEntries) {
176 Set<String> exportedPackages = new HashSet<>(coreEntry.getExportedPackages());
177 for (CoreExtensionEntry extension : extensionEntries) {
178 exportedPackages.addAll(extension.getExportedPackages());
179 }
180 return exportedPackages;
181 }
182
183
184
185
186
187
188 protected Module getCustomModule(C context, CoreExports exports) {
189 return new AbstractModule() {
190 @Override
191 protected void configure() {
192 bind(ILoggerFactory.class).toInstance(context.loggerFactory);
193 bind(CoreExports.class).toInstance(exports);
194 bind(MessageBuilderFactory.class).toInstance(context.invokerRequest.messageBuilderFactory());
195 }
196 };
197 }
198
199 protected LoggerManager createLoggerManager() {
200 return new Slf4jLoggerManager();
201 }
202
203 protected void customizeContainerConfiguration(C context, ContainerConfiguration configuration) throws Exception {}
204
205 protected void customizeContainer(C context, PlexusContainer container) throws Exception {}
206
207 protected List<Path> parseExtClasspath(C context) throws Exception {
208 ProtoSession protoSession = context.protoSession;
209 String extClassPath = protoSession.getUserProperties().get(Constants.MAVEN_EXT_CLASS_PATH);
210 if (extClassPath == null) {
211 extClassPath = protoSession.getSystemProperties().get(Constants.MAVEN_EXT_CLASS_PATH);
212 if (extClassPath != null) {
213 context.logger.warn("The property '" + Constants.MAVEN_EXT_CLASS_PATH
214 + "' has been set using a JVM system property which is deprecated. "
215 + "The property can be passed as a Maven argument or in the Maven project configuration file,"
216 + "usually located at ${session.rootDirectory}/.mvn/maven.properties.");
217 }
218 }
219 ArrayList<Path> jars = new ArrayList<>();
220 if (extClassPath != null && !extClassPath.isEmpty()) {
221 for (String jar : extClassPath.split(File.pathSeparator)) {
222 Path file = context.cwd.resolve(jar);
223 context.logger.debug(" included '" + file + "'");
224 jars.add(file);
225 }
226 }
227 return jars;
228 }
229
230 protected ClassRealm setupContainerRealm(
231 Logger logger,
232 ClassWorld classWorld,
233 ClassRealm coreRealm,
234 List<Path> extClassPath,
235 List<CoreExtensionEntry> extensions)
236 throws Exception {
237 if (!extClassPath.isEmpty() || !extensions.isEmpty()) {
238 ClassRealm extRealm = classWorld.newRealm("maven.ext", null);
239
240 extRealm.setParentRealm(coreRealm);
241
242 logger.debug("Populating class realm '" + extRealm.getId() + "'");
243
244 for (Path file : extClassPath) {
245 logger.debug(" included '" + file + "'");
246 extRealm.addURL(file.toUri().toURL());
247 }
248
249 ArrayList<CoreExtensionEntry> reversed = new ArrayList<>(extensions);
250 Collections.reverse(reversed);
251 for (CoreExtensionEntry entry : reversed) {
252 Set<String> exportedPackages = entry.getExportedPackages();
253 ClassRealm realm = entry.getClassRealm();
254 for (String exportedPackage : exportedPackages) {
255 extRealm.importFrom(realm, exportedPackage);
256 }
257 if (exportedPackages.isEmpty()) {
258
259 extRealm.importFrom(realm, realm.getId());
260 }
261 }
262
263 return extRealm;
264 }
265
266 return coreRealm;
267 }
268
269 protected List<LoadedCoreExtension> loadCoreExtensions(
270 LookupInvoker<C> invoker,
271 C context,
272 ClassRealm containerRealm,
273 Set<String> providedArtifacts,
274 List<CoreExtension> extensions)
275 throws Exception {
276 if (extensions.isEmpty()) {
277 return List.of();
278 }
279 ContainerConfiguration cc = new DefaultContainerConfiguration()
280 .setClassWorld(containerRealm.getWorld())
281 .setRealm(containerRealm)
282 .setClassPathScanning(PlexusConstants.SCANNING_INDEX)
283 .setAutoWiring(true)
284 .setJSR250Lifecycle(true)
285 .setStrictClassPathScanning(false)
286 .setName("maven");
287
288 DefaultPlexusContainer container = new DefaultPlexusContainer(cc, new AbstractModule() {
289 @Override
290 protected void configure() {
291 bind(ILoggerFactory.class).toProvider(() -> context.loggerFactory);
292 }
293 });
294
295 ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
296 Runnable settingsCleaner = null;
297 try {
298 container.setLookupRealm(null);
299 container.setLoggerManager(createLoggerManager());
300 container.getLoggerManager().setThresholds(toPlexusLoggingLevel(context.loggerLevel));
301 Thread.currentThread().setContextClassLoader(container.getContainerRealm());
302
303 settingsCleaner = invoker.settings(context, false, container.lookup(SettingsBuilder.class));
304
305 MavenExecutionRequest mer = new DefaultMavenExecutionRequest();
306 invoker.populateRequest(context, new DefaultLookup(container), mer);
307 mer = container.lookup(MavenExecutionRequestPopulator.class).populateDefaults(mer);
308 return Collections.unmodifiableList(container
309 .lookup(BootstrapCoreExtensionManager.class)
310 .loadCoreExtensions(mer, providedArtifacts, extensions));
311 } finally {
312 if (settingsCleaner != null) {
313 settingsCleaner.run();
314 }
315 try {
316 container.dispose();
317 } finally {
318 Thread.currentThread().setContextClassLoader(oldCL);
319 }
320 }
321 }
322 }