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