View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.api.plugin.testing;
20  
21  import java.io.BufferedReader;
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.Reader;
26  import java.io.StringReader;
27  import java.lang.reflect.AccessibleObject;
28  import java.lang.reflect.AnnotatedElement;
29  import java.lang.reflect.Field;
30  import java.net.URL;
31  import java.nio.file.Files;
32  import java.nio.file.Path;
33  import java.nio.file.Paths;
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.HashMap;
37  import java.util.LinkedHashSet;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Objects;
41  import java.util.Optional;
42  import java.util.Set;
43  import java.util.stream.Collectors;
44  import java.util.stream.Stream;
45  
46  import org.apache.maven.api.MojoExecution;
47  import org.apache.maven.api.Project;
48  import org.apache.maven.api.Session;
49  import org.apache.maven.api.di.Named;
50  import org.apache.maven.api.di.Priority;
51  import org.apache.maven.api.di.Provides;
52  import org.apache.maven.api.di.Singleton;
53  import org.apache.maven.api.di.testing.MavenDIExtension;
54  import org.apache.maven.api.model.Build;
55  import org.apache.maven.api.model.ConfigurationContainer;
56  import org.apache.maven.api.model.Model;
57  import org.apache.maven.api.model.Source;
58  import org.apache.maven.api.plugin.Log;
59  import org.apache.maven.api.plugin.Mojo;
60  import org.apache.maven.api.plugin.descriptor.MojoDescriptor;
61  import org.apache.maven.api.plugin.descriptor.Parameter;
62  import org.apache.maven.api.plugin.descriptor.PluginDescriptor;
63  import org.apache.maven.api.plugin.testing.stubs.MojoExecutionStub;
64  import org.apache.maven.api.plugin.testing.stubs.PluginStub;
65  import org.apache.maven.api.plugin.testing.stubs.ProducedArtifactStub;
66  import org.apache.maven.api.plugin.testing.stubs.ProjectStub;
67  import org.apache.maven.api.plugin.testing.stubs.RepositorySystemSupplier;
68  import org.apache.maven.api.plugin.testing.stubs.SessionMock;
69  import org.apache.maven.api.services.ArtifactDeployer;
70  import org.apache.maven.api.services.ArtifactFactory;
71  import org.apache.maven.api.services.ArtifactInstaller;
72  import org.apache.maven.api.services.ArtifactManager;
73  import org.apache.maven.api.services.LocalRepositoryManager;
74  import org.apache.maven.api.services.ProjectBuilder;
75  import org.apache.maven.api.services.ProjectManager;
76  import org.apache.maven.api.services.RepositoryFactory;
77  import org.apache.maven.api.services.VersionParser;
78  import org.apache.maven.api.services.xml.ModelXmlFactory;
79  import org.apache.maven.api.xml.XmlNode;
80  import org.apache.maven.api.xml.XmlService;
81  import org.apache.maven.configuration.internal.EnhancedComponentConfigurator;
82  import org.apache.maven.di.Injector;
83  import org.apache.maven.di.Key;
84  import org.apache.maven.di.impl.DIException;
85  import org.apache.maven.impl.InternalSession;
86  import org.apache.maven.impl.model.DefaultModelPathTranslator;
87  import org.apache.maven.impl.model.DefaultPathTranslator;
88  import org.apache.maven.internal.impl.DefaultLog;
89  import org.apache.maven.internal.xml.XmlPlexusConfiguration;
90  import org.apache.maven.lifecycle.internal.MojoDescriptorCreator;
91  import org.apache.maven.model.v4.MavenMerger;
92  import org.apache.maven.model.v4.MavenStaxReader;
93  import org.apache.maven.plugin.PluginParameterExpressionEvaluatorV4;
94  import org.apache.maven.plugin.descriptor.io.PluginDescriptorStaxReader;
95  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
96  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
97  import org.codehaus.plexus.component.configurator.expression.TypeAwareExpressionEvaluator;
98  import org.codehaus.plexus.util.ReflectionUtils;
99  import org.codehaus.plexus.util.xml.XmlStreamReader;
100 import org.codehaus.plexus.util.xml.Xpp3Dom;
101 import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
102 import org.eclipse.aether.RepositorySystem;
103 import org.junit.jupiter.api.extension.BeforeEachCallback;
104 import org.junit.jupiter.api.extension.ExtensionContext;
105 import org.junit.jupiter.api.extension.ParameterContext;
106 import org.junit.jupiter.api.extension.ParameterResolutionException;
107 import org.junit.jupiter.api.extension.ParameterResolver;
108 import org.junit.platform.commons.support.AnnotationSupport;
109 import org.slf4j.LoggerFactory;
110 
111 import static java.util.Objects.requireNonNull;
112 
113 /**
114  * JUnit Jupiter extension that provides support for testing Maven plugins (Mojos).
115  * This extension handles the lifecycle of Mojo instances in tests, including instantiation,
116  * configuration, and dependency injection.
117  *
118  * <p>The extension is automatically registered when using the {@link MojoTest} annotation
119  * on a test class. It provides the following features:</p>
120  * <ul>
121  *   <li>Automatic Mojo instantiation based on {@link InjectMojo} annotations</li>
122  *   <li>Parameter injection using {@link MojoParameter} annotations</li>
123  *   <li>POM configuration handling</li>
124  *   <li>Project stub creation and configuration</li>
125  *   <li>Maven session and build context setup</li>
126  *   <li>Component dependency injection</li>
127  * </ul>
128  *
129  * <p>Example usage in a test class:</p>
130  * <pre>
131  * {@code
132  * @MojoTest
133  * class MyMojoTest {
134  *     @Test
135  *     @InjectMojo(goal = "my-goal")
136  *     @MojoParameter(name = "outputDirectory", value = "${project.build.directory}/generated")
137  *     void testMojoExecution(MyMojo mojo) throws Exception {
138  *         mojo.execute();
139  *         // verify execution results
140  *     }
141  * }
142  * }
143  * </pre>
144  *
145  * <p>The extension supports two main injection scenarios:</p>
146  * <ol>
147  *   <li>Method parameter injection: Mojo instances can be injected as test method parameters</li>
148  *   <li>Field injection: Components can be injected into test class fields using {@code @Inject}</li>
149  * </ol>
150  *
151  * <p>For custom POM configurations, you can specify a POM file using the {@link InjectMojo#pom()}
152  * attribute. The extension will merge this configuration with default test project settings.</p>
153  *
154  * <p>Base directory handling:</p>
155  * <ul>
156  *   <li>Plugin basedir: The directory containing the plugin project</li>
157  *   <li>Test basedir: The directory containing test resources, configurable via {@link Basedir}</li>
158  * </ul>
159  *
160  * @see MojoTest
161  * @see InjectMojo
162  * @see MojoParameter
163  * @see Basedir
164  * @since 4.0.0
165  */
166 public class MojoExtension extends MavenDIExtension implements ParameterResolver, BeforeEachCallback {
167 
168     /** The base directory of the plugin being tested */
169     protected static String pluginBasedir;
170 
171     /** The base directory for test resources */
172     protected static String basedir;
173 
174     /**
175      * Gets the identifier for the current test method.
176      * The format is "TestClassName-testMethodName".
177      *
178      * @return the test identifier
179      */
180     public static String getTestId() {
181         return context.getRequiredTestClass().getSimpleName() + "-"
182                 + context.getRequiredTestMethod().getName();
183     }
184 
185     /**
186      * Gets the base directory for test resources.
187      * If not explicitly set via {@link Basedir}, returns the plugin base directory.
188      *
189      * @return the base directory path
190      * @throws NullPointerException if neither basedir nor plugin basedir is set
191      */
192     public static String getBasedir() {
193         return requireNonNull(basedir != null ? basedir : MavenDIExtension.basedir);
194     }
195 
196     /**
197      * Gets the base directory of the plugin being tested.
198      *
199      * @return the plugin base directory path
200      * @throws NullPointerException if plugin basedir is not set
201      */
202     public static String getPluginBasedir() {
203         return requireNonNull(pluginBasedir);
204     }
205 
206     /**
207      * Determines if this extension can resolve the given parameter.
208      * Returns true if the parameter is annotated with {@link InjectMojo} or
209      * if its declaring method is annotated with {@link InjectMojo}.
210      *
211      * @param parameterContext the context for the parameter being resolved
212      * @param extensionContext the current extension context
213      * @return true if this extension can resolve the parameter
214      */
215     @Override
216     public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
217             throws ParameterResolutionException {
218         return parameterContext.isAnnotated(InjectMojo.class)
219                 || parameterContext.getDeclaringExecutable().isAnnotationPresent(InjectMojo.class);
220     }
221 
222     @Override
223     public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
224             throws ParameterResolutionException {
225         try {
226             Class<?> holder = parameterContext.getTarget().orElseThrow().getClass();
227             PluginDescriptor descriptor = extensionContext
228                     .getStore(ExtensionContext.Namespace.GLOBAL)
229                     .get(PluginDescriptor.class, PluginDescriptor.class);
230             Model model =
231                     extensionContext.getStore(ExtensionContext.Namespace.GLOBAL).get(Model.class, Model.class);
232             InjectMojo parameterInjectMojo =
233                     parameterContext.getAnnotatedElement().getAnnotation(InjectMojo.class);
234             String goal;
235             if (parameterInjectMojo != null) {
236                 String pom = parameterInjectMojo.pom();
237                 if (pom != null && !pom.isEmpty()) {
238                     try (Reader r = openPomUrl(holder, pom, new Path[1])) {
239                         Model localModel = new MavenStaxReader().read(r);
240                         model = new MavenMerger().merge(localModel, model, false, null);
241                         model = new DefaultModelPathTranslator(new DefaultPathTranslator())
242                                 .alignToBaseDirectory(model, Paths.get(getBasedir()), null);
243                     }
244                 }
245                 goal = parameterInjectMojo.goal();
246             } else {
247                 InjectMojo methodInjectMojo = AnnotationSupport.findAnnotation(
248                                 parameterContext.getDeclaringExecutable(), InjectMojo.class)
249                         .orElse(null);
250                 if (methodInjectMojo != null) {
251                     goal = methodInjectMojo.goal();
252                 } else {
253                     goal = getGoalFromMojoImplementationClass(
254                             parameterContext.getParameter().getType());
255                 }
256             }
257 
258             Set<MojoParameter> mojoParameters = new LinkedHashSet<>();
259             for (AnnotatedElement ae :
260                     Arrays.asList(parameterContext.getDeclaringExecutable(), parameterContext.getAnnotatedElement())) {
261                 mojoParameters.addAll(AnnotationSupport.findRepeatableAnnotations(ae, MojoParameter.class));
262             }
263             String[] coord = mojoCoordinates(goal);
264 
265             XmlNode pluginConfiguration = model.getBuild().getPlugins().stream()
266                     .filter(p ->
267                             Objects.equals(p.getGroupId(), coord[0]) && Objects.equals(p.getArtifactId(), coord[1]))
268                     .map(ConfigurationContainer::getConfiguration)
269                     .findFirst()
270                     .orElseGet(() -> XmlNode.newInstance("config"));
271             List<XmlNode> children = mojoParameters.stream()
272                     .map(mp -> XmlNode.newInstance(mp.name(), mp.value()))
273                     .collect(Collectors.toList());
274             XmlNode config = XmlNode.newInstance("configuration", null, null, children, null);
275             pluginConfiguration = XmlService.merge(config, pluginConfiguration);
276 
277             // load default config
278             // pluginkey = groupId : artifactId : version : goal
279             Mojo mojo = lookup(Mojo.class, coord[0] + ":" + coord[1] + ":" + coord[2] + ":" + coord[3]);
280             for (MojoDescriptor mojoDescriptor : descriptor.getMojos()) {
281                 if (Objects.equals(mojoDescriptor.getGoal(), coord[3])) {
282                     if (pluginConfiguration != null) {
283                         pluginConfiguration = finalizeConfig(pluginConfiguration, mojoDescriptor);
284                     }
285                 }
286             }
287 
288             Session session = getInjector().getInstance(Session.class);
289             Project project = getInjector().getInstance(Project.class);
290             MojoExecution mojoExecution = getInjector().getInstance(MojoExecution.class);
291             ExpressionEvaluator evaluator = new WrapEvaluator(
292                     getInjector(), new PluginParameterExpressionEvaluatorV4(session, project, mojoExecution));
293 
294             EnhancedComponentConfigurator configurator = new EnhancedComponentConfigurator();
295             configurator.configureComponent(
296                     mojo, new XmlPlexusConfiguration(pluginConfiguration), evaluator, null, null);
297             return mojo;
298         } catch (Exception e) {
299             throw new ParameterResolutionException("Unable to resolve mojo", e);
300         }
301     }
302 
303     /**
304      * The @Mojo annotation is only retained in the class file, not at runtime,
305      * so we need to actually read the class file with ASM to find the annotation and
306      * the goal.
307      */
308     private static String getGoalFromMojoImplementationClass(Class<?> cl) throws IOException {
309         return cl.getAnnotation(Named.class).value();
310     }
311 
312     @Override
313     @SuppressWarnings("checkstyle:MethodLength")
314     public void beforeEach(ExtensionContext context) throws Exception {
315         if (pluginBasedir == null) {
316             pluginBasedir = MavenDIExtension.getBasedir();
317         }
318         basedir = AnnotationSupport.findAnnotation(context.getElement().orElseThrow(), Basedir.class)
319                 .map(Basedir::value)
320                 .orElse(pluginBasedir);
321         if (basedir != null) {
322             if (basedir.isEmpty()) {
323                 basedir = pluginBasedir + "/target/tests/"
324                         + context.getRequiredTestClass().getSimpleName() + "/"
325                         + context.getRequiredTestMethod().getName();
326             } else {
327                 basedir = basedir.replace("${basedir}", pluginBasedir);
328             }
329         }
330 
331         setContext(context);
332 
333         /*
334            binder.install(ProviderMethodsModule.forObject(context.getRequiredTestInstance()));
335            binder.requestInjection(context.getRequiredTestInstance());
336            binder.bind(Log.class).toInstance(new DefaultLog(LoggerFactory.getLogger("anonymous")));
337            binder.bind(ExtensionContext.class).toInstance(context);
338            // Load maven 4 api Services interfaces and try to bind them to the (possible) mock instances
339            // returned by the (possibly) mock InternalSession
340            try {
341                for (ClassPath.ClassInfo clazz :
342                        ClassPath.from(getClassLoader()).getAllClasses()) {
343                    if ("org.apache.maven.api.services".equals(clazz.getPackageName())) {
344                        Class<?> load = clazz.load();
345                        if (Service.class.isAssignableFrom(load)) {
346                            Class<Service> svc = (Class) load;
347                            binder.bind(svc).toProvider(() -> {
348                                try {
349                                    return getContainer()
350                                            .lookup(InternalSession.class)
351                                            .getService(svc);
352                                } catch (ComponentLookupException e) {
353                                    throw new RuntimeException("Unable to lookup service " + svc.getName());
354                                }
355                            });
356                        }
357                    }
358                }
359            } catch (Exception e) {
360                throw new RuntimeException("Unable to bind session services", e);
361            }
362 
363         */
364 
365         Path basedirPath = Paths.get(getBasedir());
366 
367         InjectMojo mojo = AnnotationSupport.findAnnotation(context.getElement().get(), InjectMojo.class)
368                 .orElse(null);
369         Model defaultModel = Model.newBuilder()
370                 .groupId("myGroupId")
371                 .artifactId("myArtifactId")
372                 .version("1.0-SNAPSHOT")
373                 .packaging("jar")
374                 .build(Build.newBuilder()
375                         .directory(basedirPath.resolve("target").toString())
376                         .outputDirectory(basedirPath.resolve("target/classes").toString())
377                         .sources(List.of(
378                                 Source.newBuilder()
379                                         .scope("main")
380                                         .lang("java")
381                                         .directory(basedirPath
382                                                 .resolve("src/main/java")
383                                                 .toString())
384                                         .build(),
385                                 Source.newBuilder()
386                                         .scope("test")
387                                         .lang("java")
388                                         .directory(basedirPath
389                                                 .resolve("src/test/java")
390                                                 .toString())
391                                         .build()))
392                         .testOutputDirectory(
393                                 basedirPath.resolve("target/test-classes").toString())
394                         .build())
395                 .build();
396         Path[] modelPath = new Path[] {null};
397         Model tmodel = null;
398         if (mojo != null) {
399             String pom = mojo.pom();
400             if (pom != null && !pom.isEmpty()) {
401                 try (Reader r = openPomUrl(context.getRequiredTestClass(), pom, modelPath)) {
402                     tmodel = new MavenStaxReader().read(r);
403                 }
404             } else {
405                 Path pomPath = basedirPath.resolve("pom.xml");
406                 if (Files.exists(pomPath)) {
407                     try (Reader r = Files.newBufferedReader(pomPath)) {
408                         tmodel = new MavenStaxReader().read(r);
409                         modelPath[0] = pomPath;
410                     }
411                 }
412             }
413         }
414         Model model;
415         if (tmodel == null) {
416             model = defaultModel;
417         } else {
418             model = new MavenMerger().merge(tmodel, defaultModel, false, null);
419         }
420         tmodel = new DefaultModelPathTranslator(new DefaultPathTranslator())
421                 .alignToBaseDirectory(tmodel, Paths.get(getBasedir()), null);
422         context.getStore(ExtensionContext.Namespace.GLOBAL).put(Model.class, tmodel);
423 
424         // mojo execution
425         // Map<Object, Object> map = getInjector().getContext().getContextData();
426         PluginDescriptor pluginDescriptor;
427         ClassLoader classLoader = context.getRequiredTestClass().getClassLoader();
428         try (InputStream is = requireNonNull(
429                         classLoader.getResourceAsStream(getPluginDescriptorLocation()),
430                         "Unable to find plugin descriptor: " + getPluginDescriptorLocation());
431                 Reader reader = new BufferedReader(new XmlStreamReader(is))) {
432             // new InterpolationFilterReader(reader, map, "${", "}");
433             pluginDescriptor = new PluginDescriptorStaxReader().read(reader);
434         }
435         context.getStore(ExtensionContext.Namespace.GLOBAL).put(PluginDescriptor.class, pluginDescriptor);
436         // for (ComponentDescriptor<?> desc : pluginDescriptor.getComponents()) {
437         //    getContainer().addComponentDescriptor(desc);
438         // }
439 
440         @SuppressWarnings({"unused", "MagicNumber"})
441         class Foo {
442 
443             @Provides
444             @Singleton
445             @Priority(-10)
446             private InternalSession createSession() {
447                 MojoTest mojoTest = context.getRequiredTestClass().getAnnotation(MojoTest.class);
448                 if (mojoTest != null && mojoTest.realSession()) {
449                     // Try to create a real session using ApiRunner without compile-time dependency
450                     try {
451                         Class<?> apiRunner = Class.forName("org.apache.maven.impl.standalone.ApiRunner");
452                         Object session = apiRunner.getMethod("createSession").invoke(null);
453                         return (InternalSession) session;
454                     } catch (Throwable t) {
455                         // Explicit request: do not fall back; abort the test with details instead of mocking
456                         throw new org.opentest4j.TestAbortedException(
457                                 "@MojoTest(realSession=true) requested but could not create a real session.", t);
458                     }
459                 }
460                 return SessionMock.getMockSession(getBasedir());
461             }
462 
463             @Provides
464             @Singleton
465             @Priority(-10)
466             private Project createProject(InternalSession s) {
467                 ProjectStub stub = new ProjectStub();
468                 if (!"pom".equals(model.getPackaging())) {
469                     ProducedArtifactStub artifact = new ProducedArtifactStub(
470                             model.getGroupId(), model.getArtifactId(), "", model.getVersion(), model.getPackaging());
471                     stub.setMainArtifact(artifact);
472                 }
473                 stub.setModel(model);
474                 stub.setBasedir(Paths.get(MojoExtension.getBasedir()));
475                 stub.setPomPath(modelPath[0]);
476                 s.getService(ArtifactManager.class).setPath(stub.getPomArtifact(), modelPath[0]);
477                 return stub;
478             }
479 
480             @Provides
481             @Singleton
482             @Priority(-10)
483             private MojoExecution createMojoExecution() {
484                 MojoExecutionStub mes = new MojoExecutionStub("executionId", null);
485                 if (mojo != null) {
486                     String goal = mojo.goal();
487                     int idx = goal.lastIndexOf(':');
488                     if (idx >= 0) {
489                         goal = goal.substring(idx + 1);
490                     }
491                     mes.setGoal(goal);
492                     for (MojoDescriptor md : pluginDescriptor.getMojos()) {
493                         if (goal.equals(md.getGoal())) {
494                             mes.setDescriptor(md);
495                         }
496                     }
497                     requireNonNull(mes.getDescriptor());
498                 }
499                 PluginStub plugin = new PluginStub();
500                 plugin.setDescriptor(pluginDescriptor);
501                 mes.setPlugin(plugin);
502                 return mes;
503             }
504 
505             @Provides
506             @Singleton
507             @Priority(-10)
508             private Log createLog() {
509                 return new DefaultLog(LoggerFactory.getLogger("anonymous"));
510             }
511 
512             @Provides
513             static RepositorySystemSupplier newRepositorySystemSupplier() {
514                 return new RepositorySystemSupplier();
515             }
516 
517             @Provides
518             static RepositorySystem newRepositorySystem(RepositorySystemSupplier repositorySystemSupplier) {
519                 return repositorySystemSupplier.getRepositorySystem();
520             }
521 
522             @Provides
523             @Priority(10)
524             static RepositoryFactory newRepositoryFactory(Session session) {
525                 return session.getService(RepositoryFactory.class);
526             }
527 
528             @Provides
529             @Priority(10)
530             static VersionParser newVersionParser(Session session) {
531                 return session.getService(VersionParser.class);
532             }
533 
534             @Provides
535             @Priority(10)
536             static LocalRepositoryManager newLocalRepositoryManager(Session session) {
537                 return session.getService(LocalRepositoryManager.class);
538             }
539 
540             @Provides
541             @Priority(10)
542             static ArtifactInstaller newArtifactInstaller(Session session) {
543                 return session.getService(ArtifactInstaller.class);
544             }
545 
546             @Provides
547             @Priority(10)
548             static ArtifactDeployer newArtifactDeployer(Session session) {
549                 return session.getService(ArtifactDeployer.class);
550             }
551 
552             @Provides
553             @Priority(10)
554             static ArtifactManager newArtifactManager(Session session) {
555                 return session.getService(ArtifactManager.class);
556             }
557 
558             @Provides
559             @Priority(10)
560             static ProjectManager newProjectManager(Session session) {
561                 return session.getService(ProjectManager.class);
562             }
563 
564             @Provides
565             @Priority(10)
566             static ArtifactFactory newArtifactFactory(Session session) {
567                 return session.getService(ArtifactFactory.class);
568             }
569 
570             @Provides
571             @Priority(10)
572             static ProjectBuilder newProjectBuilder(Session session) {
573                 return session.getService(ProjectBuilder.class);
574             }
575 
576             @Provides
577             @Priority(10)
578             static ModelXmlFactory newModelXmlFactory(Session session) {
579                 return session.getService(ModelXmlFactory.class);
580             }
581         }
582 
583         getInjector().bindInstance(Foo.class, new Foo());
584 
585         getInjector().injectInstance(context.getRequiredTestInstance());
586 
587         //        SessionScope sessionScope = getInjector().getInstance(SessionScope.class);
588         //        sessionScope.enter();
589         //        sessionScope.seed(Session.class, s);
590         //        sessionScope.seed(InternalSession.class, s);
591 
592         //        MojoExecutionScope mojoExecutionScope = getInjector().getInstance(MojoExecutionScope.class);
593         //        mojoExecutionScope.enter();
594         //        mojoExecutionScope.seed(Project.class, p);
595         //        mojoExecutionScope.seed(MojoExecution.class, me);
596     }
597 
598     private Reader openPomUrl(Class<?> holder, String pom, Path[] modelPath) throws IOException {
599         if (pom.startsWith("file:")) {
600             Path path = Paths.get(getBasedir()).resolve(pom.substring("file:".length()));
601             modelPath[0] = path;
602             return Files.newBufferedReader(path);
603         } else if (pom.startsWith("classpath:")) {
604             URL url = holder.getResource(pom.substring("classpath:".length()));
605             if (url == null) {
606                 throw new IllegalStateException("Unable to find pom on classpath: " + pom);
607             }
608             return new XmlStreamReader(url.openStream());
609         } else if (pom.contains("<project>")) {
610             return new StringReader(pom);
611         } else {
612             Path path = Paths.get(getBasedir()).resolve(pom);
613             modelPath[0] = path;
614             return Files.newBufferedReader(path);
615         }
616     }
617 
618     protected String getPluginDescriptorLocation() {
619         return "META-INF/maven/plugin.xml";
620     }
621 
622     protected String[] mojoCoordinates(String goal) throws Exception {
623         if (goal.matches(".*:.*:.*:.*")) {
624             return goal.split(":");
625         } else {
626             Path pluginPom = Paths.get(getPluginBasedir(), "pom.xml");
627             Xpp3Dom pluginPomDom = Xpp3DomBuilder.build(Files.newBufferedReader(pluginPom));
628             String artifactId = pluginPomDom.getChild("artifactId").getValue();
629             String groupId = resolveFromRootThenParent(pluginPomDom, "groupId");
630             String version = resolveFromRootThenParent(pluginPomDom, "version");
631             return new String[] {groupId, artifactId, version, goal};
632         }
633     }
634 
635     private XmlNode finalizeConfig(XmlNode config, MojoDescriptor mojoDescriptor) {
636         List<XmlNode> children = new ArrayList<>();
637         if (mojoDescriptor != null) {
638             XmlNode defaultConfiguration;
639             defaultConfiguration = MojoDescriptorCreator.convert(mojoDescriptor);
640             for (Parameter parameter : mojoDescriptor.getParameters()) {
641                 XmlNode parameterConfiguration = config.child(parameter.getName());
642                 if (parameterConfiguration == null) {
643                     parameterConfiguration = config.child(parameter.getAlias());
644                 }
645                 XmlNode parameterDefaults = defaultConfiguration.child(parameter.getName());
646                 parameterConfiguration = XmlNode.merge(parameterConfiguration, parameterDefaults, Boolean.TRUE);
647                 if (parameterConfiguration != null) {
648                     Map<String, String> attributes = new HashMap<>(parameterConfiguration.attributes());
649                     // if (isEmpty(parameterConfiguration.getAttribute("implementation"))
650                     //         && !isEmpty(parameter.getImplementation())) {
651                     //     attributes.put("implementation", parameter.getImplementation());
652                     // }
653                     parameterConfiguration = XmlNode.newInstance(
654                             parameter.getName(),
655                             parameterConfiguration.value(),
656                             attributes,
657                             parameterConfiguration.children(),
658                             parameterConfiguration.inputLocation());
659 
660                     children.add(parameterConfiguration);
661                 }
662             }
663         }
664         return XmlNode.newInstance("configuration", null, null, children, null);
665     }
666 
667     private static Optional<Xpp3Dom> child(Xpp3Dom element, String name) {
668         return Optional.ofNullable(element.getChild(name));
669     }
670 
671     private static Stream<Xpp3Dom> children(Xpp3Dom element) {
672         return Stream.of(element.getChildren());
673     }
674 
675     public static XmlNode extractPluginConfiguration(String artifactId, Xpp3Dom pomDom) throws Exception {
676         Xpp3Dom pluginConfigurationElement = child(pomDom, "build")
677                 .flatMap(buildElement -> child(buildElement, "plugins"))
678                 .map(MojoExtension::children)
679                 .orElseGet(Stream::empty)
680                 .filter(e -> e.getChild("artifactId").getValue().equals(artifactId))
681                 .findFirst()
682                 .flatMap(buildElement -> child(buildElement, "configuration"))
683                 .orElse(Xpp3DomBuilder.build(new StringReader("<configuration/>")));
684         return pluginConfigurationElement.getDom();
685     }
686 
687     /**
688      * sometimes the parent element might contain the correct value so generalize that access
689      *
690      * TODO find out where this is probably done elsewhere
691      */
692     private static String resolveFromRootThenParent(Xpp3Dom pluginPomDom, String element) throws Exception {
693         return Optional.ofNullable(child(pluginPomDom, element).orElseGet(() -> child(pluginPomDom, "parent")
694                         .flatMap(e -> child(e, element))
695                         .orElse(null)))
696                 .map(Xpp3Dom::getValue)
697                 .orElseThrow(() -> new Exception("unable to determine " + element));
698     }
699 
700     /**
701      * Convenience method to obtain the value of a variable on a mojo that might not have a getter.
702      * <br>
703      * NOTE: the caller is responsible for casting to what the desired type is.
704      */
705     public static Object getVariableValueFromObject(Object object, String variable) throws IllegalAccessException {
706         Field field = ReflectionUtils.getFieldByNameIncludingSuperclasses(variable, object.getClass());
707         field.setAccessible(true);
708         return field.get(object);
709     }
710 
711     /**
712      * Convenience method to obtain all variables and values from the mojo (including its superclasses)
713      * <br>
714      * Note: the values in the map are of type Object so the caller is responsible for casting to desired types.
715      */
716     public static Map<String, Object> getVariablesAndValuesFromObject(Object object) throws IllegalAccessException {
717         return getVariablesAndValuesFromObject(object.getClass(), object);
718     }
719 
720     /**
721      * Convenience method to obtain all variables and values from the mojo (including its superclasses)
722      * <br>
723      * Note: the values in the map are of type Object so the caller is responsible for casting to desired types.
724      *
725      * @return map of variable names and values
726      */
727     public static Map<String, Object> getVariablesAndValuesFromObject(Class<?> clazz, Object object)
728             throws IllegalAccessException {
729         Map<String, Object> map = new HashMap<>();
730         Field[] fields = clazz.getDeclaredFields();
731         AccessibleObject.setAccessible(fields, true);
732         for (Field field : fields) {
733             map.put(field.getName(), field.get(object));
734         }
735         Class<?> superclass = clazz.getSuperclass();
736         if (!Object.class.equals(superclass)) {
737             map.putAll(getVariablesAndValuesFromObject(superclass, object));
738         }
739         return map;
740     }
741 
742     /**
743      * Convenience method to set values to variables in objects that don't have setters
744      */
745     public static void setVariableValueToObject(Object object, String variable, Object value)
746             throws IllegalAccessException {
747         Field field = ReflectionUtils.getFieldByNameIncludingSuperclasses(variable, object.getClass());
748         requireNonNull(field, "Field " + variable + " not found");
749         field.setAccessible(true);
750         field.set(object, value);
751     }
752 
753     static class WrapEvaluator implements TypeAwareExpressionEvaluator {
754 
755         private final Injector injector;
756         private final TypeAwareExpressionEvaluator evaluator;
757 
758         WrapEvaluator(Injector injector, TypeAwareExpressionEvaluator evaluator) {
759             this.injector = injector;
760             this.evaluator = evaluator;
761         }
762 
763         @Override
764         public Object evaluate(String expression) throws ExpressionEvaluationException {
765             return evaluate(expression, null);
766         }
767 
768         @Override
769         public Object evaluate(String expression, Class<?> type) throws ExpressionEvaluationException {
770             Object value = evaluator.evaluate(expression, type);
771             if (value == null) {
772                 String expr = stripTokens(expression);
773                 if (expr != null) {
774                     try {
775                         value = injector.getInstance(Key.of(type, expr));
776                     } catch (DIException e) {
777                         // nothing
778                     }
779                 }
780             }
781             return value;
782         }
783 
784         private String stripTokens(String expr) {
785             if (expr.startsWith("${") && expr.endsWith("}")) {
786                 return expr.substring(2, expr.length() - 1);
787             }
788             return null;
789         }
790 
791         @Override
792         public File alignToBaseDirectory(File path) {
793             return evaluator.alignToBaseDirectory(path);
794         }
795     }
796 
797     /*
798     private Scope getScopeInstanceOrNull(final Injector injector, final Binding<?> binding) {
799         return binding.acceptScopingVisitor(new DefaultBindingScopingVisitor<Scope>() {
800 
801             @Override
802             public Scope visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
803                 throw new RuntimeException(String.format(
804                         "I don't know how to handle the scopeAnnotation: %s", scopeAnnotation.getCanonicalName()));
805             }
806 
807             @Override
808             public Scope visitNoScoping() {
809                 if (binding instanceof LinkedKeyBinding) {
810                     Binding<?> childBinding = injector.getBinding(((LinkedKeyBinding) binding).getLinkedKey());
811                     return getScopeInstanceOrNull(injector, childBinding);
812                 }
813                 return null;
814             }
815 
816             @Override
817             public Scope visitEagerSingleton() {
818                 return Scopes.SINGLETON;
819             }
820 
821             public Scope visitScope(Scope scope) {
822                 return scope;
823             }
824         });
825     }*/
826 
827 }