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.InvokerRequest;
35 import org.apache.maven.api.cli.Logger;
36 import org.apache.maven.api.cli.extensions.CoreExtension;
37 import org.apache.maven.api.services.MessageBuilderFactory;
38 import org.apache.maven.api.services.SettingsBuilder;
39 import org.apache.maven.cling.extensions.BootstrapCoreExtensionManager;
40 import org.apache.maven.cling.extensions.ExtensionConfigurationModule;
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 org.apache.maven.cling.invoker.Utils.toPlexusLoggingLevel;
64
65
66
67
68
69
70 public class PlexusContainerCapsuleFactory<C extends LookupContext> implements ContainerCapsuleFactory<C> {
71 @Override
72 public ContainerCapsule createContainerCapsule(LookupInvoker<C> invoker, C context) throws Exception {
73 return new PlexusContainerCapsule(
74 context, Thread.currentThread().getContextClassLoader(), container(invoker, context));
75 }
76
77 protected DefaultPlexusContainer container(LookupInvoker<C> invoker, C context) throws Exception {
78 ClassWorld classWorld = invoker.protoLookup.lookup(ClassWorld.class);
79 ClassRealm coreRealm = classWorld.getClassRealm("plexus.core");
80 List<Path> extClassPath = parseExtClasspath(context);
81 CoreExtensionEntry coreEntry = CoreExtensionEntry.discoverFrom(coreRealm);
82 List<CoreExtensionEntry> extensions =
83 loadCoreExtensions(invoker, context, coreRealm, coreEntry.getExportedArtifacts());
84 ClassRealm containerRealm =
85 setupContainerRealm(context.logger, classWorld, coreRealm, extClassPath, extensions);
86 ContainerConfiguration cc = new DefaultContainerConfiguration()
87 .setClassWorld(classWorld)
88 .setRealm(containerRealm)
89 .setClassPathScanning(PlexusConstants.SCANNING_INDEX)
90 .setAutoWiring(true)
91 .setJSR250Lifecycle(true)
92 .setStrictClassPathScanning(true)
93 .setName("maven");
94 customizeContainerConfiguration(context, cc);
95
96 CoreExports exports = new CoreExports(
97 containerRealm,
98 collectExportedArtifacts(coreEntry, extensions),
99 collectExportedPackages(coreEntry, extensions));
100 Thread.currentThread().setContextClassLoader(containerRealm);
101 DefaultPlexusContainer container = new DefaultPlexusContainer(cc, getCustomModule(context, exports));
102
103
104 container.setLookupRealm(null);
105 Thread.currentThread().setContextClassLoader(container.getContainerRealm());
106
107 container.setLoggerManager(createLoggerManager());
108 ProtoSession protoSession = context.protoSession;
109 UnaryOperator<String> extensionSource = expression -> {
110 String value = protoSession.getUserProperties().get(expression);
111 if (value == null) {
112 value = protoSession.getSystemProperties().get(expression);
113 }
114 return value;
115 };
116 for (CoreExtensionEntry extension : extensions) {
117 container.discoverComponents(
118 extension.getClassRealm(),
119 new AbstractModule() {
120 @Override
121 protected void configure() {
122 try {
123 container.lookup(Injector.class).discover(extension.getClassRealm());
124 } catch (Throwable e) {
125 context.logger.warn("Maven DI failure", e);
126 }
127 }
128 },
129 new SessionScopeModule(container.lookup(SessionScope.class)),
130 new MojoExecutionScopeModule(container.lookup(MojoExecutionScope.class)),
131 new ExtensionConfigurationModule(extension, extensionSource));
132 }
133
134 container.getLoggerManager().setThresholds(toPlexusLoggingLevel(context.loggerLevel));
135 customizeContainer(context, container);
136
137 return container;
138 }
139
140 protected Set<String> collectExportedArtifacts(
141 CoreExtensionEntry coreEntry, List<CoreExtensionEntry> extensionEntries) {
142 Set<String> exportedArtifacts = new HashSet<>(coreEntry.getExportedArtifacts());
143 for (CoreExtensionEntry extension : extensionEntries) {
144 exportedArtifacts.addAll(extension.getExportedArtifacts());
145 }
146 return exportedArtifacts;
147 }
148
149 protected Set<String> collectExportedPackages(
150 CoreExtensionEntry coreEntry, List<CoreExtensionEntry> extensionEntries) {
151 Set<String> exportedPackages = new HashSet<>(coreEntry.getExportedPackages());
152 for (CoreExtensionEntry extension : extensionEntries) {
153 exportedPackages.addAll(extension.getExportedPackages());
154 }
155 return exportedPackages;
156 }
157
158
159
160
161
162
163 protected Module getCustomModule(C context, CoreExports exports) {
164 return new AbstractModule() {
165 @Override
166 protected void configure() {
167 bind(ILoggerFactory.class).toInstance(context.loggerFactory);
168 bind(CoreExports.class).toInstance(exports);
169 bind(MessageBuilderFactory.class).toInstance(context.invokerRequest.messageBuilderFactory());
170 }
171 };
172 }
173
174 protected LoggerManager createLoggerManager() {
175 return new Slf4jLoggerManager();
176 }
177
178 protected void customizeContainerConfiguration(C context, ContainerConfiguration configuration) throws Exception {}
179
180 protected void customizeContainer(C context, PlexusContainer container) throws Exception {}
181
182 protected List<Path> parseExtClasspath(C context) throws Exception {
183 ProtoSession protoSession = context.protoSession;
184 String extClassPath = protoSession.getUserProperties().get(Constants.MAVEN_EXT_CLASS_PATH);
185 if (extClassPath == null) {
186 extClassPath = protoSession.getSystemProperties().get(Constants.MAVEN_EXT_CLASS_PATH);
187 if (extClassPath != null) {
188 context.logger.warn("The property '" + Constants.MAVEN_EXT_CLASS_PATH
189 + "' has been set using a JVM system property which is deprecated. "
190 + "The property can be passed as a Maven argument or in the Maven project configuration file,"
191 + "usually located at ${session.rootDirectory}/.mvn/maven.properties.");
192 }
193 }
194 ArrayList<Path> jars = new ArrayList<>();
195 if (extClassPath != null && !extClassPath.isEmpty()) {
196 for (String jar : extClassPath.split(File.pathSeparator)) {
197 Path file = context.cwd.resolve(jar);
198 context.logger.debug(" included '" + file + "'");
199 jars.add(file);
200 }
201 }
202 return jars;
203 }
204
205 protected ClassRealm setupContainerRealm(
206 Logger logger,
207 ClassWorld classWorld,
208 ClassRealm coreRealm,
209 List<Path> extClassPath,
210 List<CoreExtensionEntry> extensions)
211 throws Exception {
212 if (!extClassPath.isEmpty() || !extensions.isEmpty()) {
213 ClassRealm extRealm = classWorld.newRealm("maven.ext", null);
214
215 extRealm.setParentRealm(coreRealm);
216
217 logger.debug("Populating class realm '" + extRealm.getId() + "'");
218
219 for (Path file : extClassPath) {
220 logger.debug(" included '" + file + "'");
221 extRealm.addURL(file.toUri().toURL());
222 }
223
224 ArrayList<CoreExtensionEntry> reversed = new ArrayList<>(extensions);
225 Collections.reverse(reversed);
226 for (CoreExtensionEntry entry : reversed) {
227 Set<String> exportedPackages = entry.getExportedPackages();
228 ClassRealm realm = entry.getClassRealm();
229 for (String exportedPackage : exportedPackages) {
230 extRealm.importFrom(realm, exportedPackage);
231 }
232 if (exportedPackages.isEmpty()) {
233
234 extRealm.importFrom(realm, realm.getId());
235 }
236 }
237
238 return extRealm;
239 }
240
241 return coreRealm;
242 }
243
244 protected List<CoreExtensionEntry> loadCoreExtensions(
245 LookupInvoker<C> invoker, C context, ClassRealm containerRealm, Set<String> providedArtifacts)
246 throws Exception {
247 InvokerRequest invokerRequest = context.invokerRequest;
248 if (invokerRequest.coreExtensions().isEmpty()
249 || invokerRequest.coreExtensions().get().isEmpty()) {
250 return Collections.emptyList();
251 }
252
253 List<CoreExtension> extensions = invokerRequest.coreExtensions().get();
254 ContainerConfiguration cc = new DefaultContainerConfiguration()
255 .setClassWorld(containerRealm.getWorld())
256 .setRealm(containerRealm)
257 .setClassPathScanning(PlexusConstants.SCANNING_INDEX)
258 .setAutoWiring(true)
259 .setJSR250Lifecycle(true)
260 .setStrictClassPathScanning(true)
261 .setName("maven");
262
263 DefaultPlexusContainer container = new DefaultPlexusContainer(cc, new AbstractModule() {
264 @Override
265 protected void configure() {
266 bind(ILoggerFactory.class).toProvider(() -> context.loggerFactory);
267 }
268 });
269
270 ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
271 Runnable settingsCleaner = null;
272 try {
273 container.setLookupRealm(null);
274 container.setLoggerManager(createLoggerManager());
275 container.getLoggerManager().setThresholds(toPlexusLoggingLevel(context.loggerLevel));
276 Thread.currentThread().setContextClassLoader(container.getContainerRealm());
277
278 settingsCleaner = invoker.settings(context, false, container.lookup(SettingsBuilder.class));
279
280 MavenExecutionRequest mer = new DefaultMavenExecutionRequest();
281 invoker.populateRequest(context, new DefaultLookup(container), mer);
282 mer = container.lookup(MavenExecutionRequestPopulator.class).populateDefaults(mer);
283 return Collections.unmodifiableList(container
284 .lookup(BootstrapCoreExtensionManager.class)
285 .loadCoreExtensions(mer, providedArtifacts, extensions));
286 } finally {
287 if (settingsCleaner != null) {
288 settingsCleaner.run();
289 }
290 try {
291 container.dispose();
292 } finally {
293 Thread.currentThread().setContextClassLoader(oldCL);
294 }
295 }
296 }
297 }