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