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 = protoSession.getEffectiveProperties()::get;
124 List<Throwable> failures = new ArrayList<>();
125 for (LoadedCoreExtension extension : loadedExtensions) {
126 container.discoverComponents(
127 extension.entry().getClassRealm(),
128 new AbstractModule() {
129 @Override
130 protected void configure() {
131 try {
132 container
133 .lookup(Injector.class)
134 .discover(extension.entry().getClassRealm());
135 } catch (Throwable e) {
136 failures.add(new IllegalStateException(
137 "Injection failure in "
138 + extension.coreExtension().getId(),
139 e));
140 }
141 }
142 },
143 new SessionScopeModule(container.lookup(SessionScope.class)),
144 new MojoExecutionScopeModule(container.lookup(MojoExecutionScope.class)),
145 new ExtensionConfigurationModule(extension.entry(), extensionSource));
146 }
147 if (!failures.isEmpty()) {
148 IllegalStateException mavenDiFailed = new IllegalStateException(
149 "Maven dependency injection failed for at least one of the registered core extension");
150 failures.forEach(mavenDiFailed::addSuppressed);
151 throw mavenDiFailed;
152 }
153 container.getLoggerManager().setThresholds(toPlexusLoggingLevel(context.loggerLevel));
154 customizeContainer(context, container);
155
156 return container;
157 }
158
159 protected Set<String> collectExportedArtifacts(
160 CoreExtensionEntry coreEntry, List<CoreExtensionEntry> extensionEntries) {
161 Set<String> exportedArtifacts = new HashSet<>(coreEntry.getExportedArtifacts());
162 for (CoreExtensionEntry extension : extensionEntries) {
163 exportedArtifacts.addAll(extension.getExportedArtifacts());
164 }
165 return exportedArtifacts;
166 }
167
168 protected Set<String> collectExportedPackages(
169 CoreExtensionEntry coreEntry, List<CoreExtensionEntry> extensionEntries) {
170 Set<String> exportedPackages = new HashSet<>(coreEntry.getExportedPackages());
171 for (CoreExtensionEntry extension : extensionEntries) {
172 exportedPackages.addAll(extension.getExportedPackages());
173 }
174 return exportedPackages;
175 }
176
177
178
179
180
181
182 protected Module getCustomModule(C context, CoreExports exports) {
183 return new AbstractModule() {
184 @Override
185 protected void configure() {
186 bind(ILoggerFactory.class).toInstance(context.loggerFactory);
187 bind(CoreExports.class).toInstance(exports);
188 bind(MessageBuilderFactory.class).toInstance(context.invokerRequest.messageBuilderFactory());
189 }
190 };
191 }
192
193 protected LoggerManager createLoggerManager() {
194 return new Slf4jLoggerManager();
195 }
196
197 protected void customizeContainerConfiguration(C context, ContainerConfiguration configuration) throws Exception {}
198
199 protected void customizeContainer(C context, PlexusContainer container) throws Exception {}
200
201 protected List<Path> parseExtClasspath(C context) throws Exception {
202 ProtoSession protoSession = context.protoSession;
203 String extClassPath = protoSession.getUserProperties().get(Constants.MAVEN_EXT_CLASS_PATH);
204 if (extClassPath == null) {
205 extClassPath = protoSession.getSystemProperties().get(Constants.MAVEN_EXT_CLASS_PATH);
206 if (extClassPath != null) {
207 context.logger.warn("The property '" + Constants.MAVEN_EXT_CLASS_PATH
208 + "' has been set using a JVM system property which is deprecated. "
209 + "The property can be passed as a Maven argument or in the Maven project configuration file,"
210 + "usually located at ${session.rootDirectory}/.mvn/maven.properties.");
211 }
212 }
213 ArrayList<Path> jars = new ArrayList<>();
214 if (extClassPath != null && !extClassPath.isEmpty()) {
215 for (String jar : extClassPath.split(File.pathSeparator)) {
216 Path file = context.cwd.resolve(jar);
217 context.logger.debug(" included '" + file + "'");
218 jars.add(file);
219 }
220 }
221 return jars;
222 }
223
224 protected ClassRealm setupContainerRealm(
225 Logger logger,
226 ClassWorld classWorld,
227 ClassRealm coreRealm,
228 List<Path> extClassPath,
229 List<CoreExtensionEntry> extensions)
230 throws Exception {
231 if (!extClassPath.isEmpty() || !extensions.isEmpty()) {
232 ClassRealm extRealm = classWorld.newRealm("maven.ext", null);
233
234 extRealm.setParentRealm(coreRealm);
235
236 logger.debug("Populating class realm '" + extRealm.getId() + "'");
237
238 for (Path file : extClassPath) {
239 logger.debug(" included '" + file + "'");
240 extRealm.addURL(file.toUri().toURL());
241 }
242
243 ArrayList<CoreExtensionEntry> reversed = new ArrayList<>(extensions);
244 Collections.reverse(reversed);
245 for (CoreExtensionEntry entry : reversed) {
246 Set<String> exportedPackages = entry.getExportedPackages();
247 ClassRealm realm = entry.getClassRealm();
248 for (String exportedPackage : exportedPackages) {
249 extRealm.importFrom(realm, exportedPackage);
250 }
251 if (exportedPackages.isEmpty()) {
252
253 extRealm.importFrom(realm, realm.getId());
254 }
255 }
256
257 return extRealm;
258 }
259
260 return coreRealm;
261 }
262
263 protected List<LoadedCoreExtension> loadCoreExtensions(
264 LookupInvoker<C> invoker,
265 C context,
266 ClassRealm containerRealm,
267 Set<String> providedArtifacts,
268 List<CoreExtension> extensions)
269 throws Exception {
270 if (extensions.isEmpty()) {
271 return List.of();
272 }
273 ContainerConfiguration cc = new DefaultContainerConfiguration()
274 .setClassWorld(containerRealm.getWorld())
275 .setRealm(containerRealm)
276 .setClassPathScanning(PlexusConstants.SCANNING_INDEX)
277 .setAutoWiring(true)
278 .setJSR250Lifecycle(true)
279 .setStrictClassPathScanning(false)
280 .setName("maven");
281
282 DefaultPlexusContainer container = new DefaultPlexusContainer(cc, new AbstractModule() {
283 @Override
284 protected void configure() {
285 bind(ILoggerFactory.class).toProvider(() -> context.loggerFactory);
286 }
287 });
288
289 ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
290 Runnable settingsCleaner = null;
291 try {
292 container.setLookupRealm(null);
293 container.setLoggerManager(createLoggerManager());
294 container.getLoggerManager().setThresholds(toPlexusLoggingLevel(context.loggerLevel));
295 Thread.currentThread().setContextClassLoader(container.getContainerRealm());
296
297 settingsCleaner = invoker.settings(context, false, container.lookup(SettingsBuilder.class));
298
299 MavenExecutionRequest mer = new DefaultMavenExecutionRequest();
300 invoker.populateRequest(context, new DefaultLookup(container), mer);
301 mer = container.lookup(MavenExecutionRequestPopulator.class).populateDefaults(mer);
302 return Collections.unmodifiableList(container
303 .lookup(BootstrapCoreExtensionManager.class)
304 .loadCoreExtensions(mer, providedArtifacts, extensions));
305 } finally {
306 if (settingsCleaner != null) {
307 settingsCleaner.run();
308 }
309 try {
310 container.dispose();
311 } finally {
312 Thread.currentThread().setContextClassLoader(oldCL);
313 }
314 }
315 }
316 }