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.plugin.Log;
58  import org.apache.maven.api.plugin.Mojo;
59  import org.apache.maven.api.plugin.descriptor.MojoDescriptor;
60  import org.apache.maven.api.plugin.descriptor.Parameter;
61  import org.apache.maven.api.plugin.descriptor.PluginDescriptor;
62  import org.apache.maven.api.plugin.testing.stubs.MojoExecutionStub;
63  import org.apache.maven.api.plugin.testing.stubs.PluginStub;
64  import org.apache.maven.api.plugin.testing.stubs.ProducedArtifactStub;
65  import org.apache.maven.api.plugin.testing.stubs.ProjectStub;
66  import org.apache.maven.api.plugin.testing.stubs.RepositorySystemSupplier;
67  import org.apache.maven.api.plugin.testing.stubs.SessionMock;
68  import org.apache.maven.api.services.ArtifactDeployer;
69  import org.apache.maven.api.services.ArtifactFactory;
70  import org.apache.maven.api.services.ArtifactInstaller;
71  import org.apache.maven.api.services.ArtifactManager;
72  import org.apache.maven.api.services.LocalRepositoryManager;
73  import org.apache.maven.api.services.ProjectBuilder;
74  import org.apache.maven.api.services.ProjectManager;
75  import org.apache.maven.api.services.RepositoryFactory;
76  import org.apache.maven.api.services.VersionParser;
77  import org.apache.maven.api.services.xml.ModelXmlFactory;
78  import org.apache.maven.api.xml.XmlNode;
79  import org.apache.maven.configuration.internal.EnhancedComponentConfigurator;
80  import org.apache.maven.di.Injector;
81  import org.apache.maven.di.Key;
82  import org.apache.maven.di.impl.DIException;
83  import org.apache.maven.internal.impl.DefaultLog;
84  import org.apache.maven.internal.impl.InternalSession;
85  import org.apache.maven.internal.impl.model.DefaultModelPathTranslator;
86  import org.apache.maven.internal.impl.model.DefaultPathTranslator;
87  import org.apache.maven.internal.xml.XmlNodeImpl;
88  import org.apache.maven.internal.xml.XmlPlexusConfiguration;
89  import org.apache.maven.lifecycle.internal.MojoDescriptorCreator;
90  import org.apache.maven.model.v4.MavenMerger;
91  import org.apache.maven.model.v4.MavenStaxReader;
92  import org.apache.maven.plugin.PluginParameterExpressionEvaluatorV4;
93  import org.apache.maven.plugin.descriptor.io.PluginDescriptorStaxReader;
94  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
95  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
96  import org.codehaus.plexus.component.configurator.expression.TypeAwareExpressionEvaluator;
97  import org.codehaus.plexus.util.ReflectionUtils;
98  import org.codehaus.plexus.util.xml.XmlStreamReader;
99  import org.codehaus.plexus.util.xml.Xpp3Dom;
100 import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
101 import org.eclipse.aether.RepositorySystem;
102 import org.junit.jupiter.api.extension.BeforeEachCallback;
103 import org.junit.jupiter.api.extension.ExtensionContext;
104 import org.junit.jupiter.api.extension.ParameterContext;
105 import org.junit.jupiter.api.extension.ParameterResolutionException;
106 import org.junit.jupiter.api.extension.ParameterResolver;
107 import org.junit.platform.commons.support.AnnotationSupport;
108 import org.slf4j.LoggerFactory;
109 
110 import static java.util.Objects.requireNonNull;
111 
112 /**
113  * JUnit extension to help testing Mojos. The extension should be automatically registered
114  * by adding the {@link MojoTest} annotation on the test class.
115  *
116  * @see MojoTest
117  * @see InjectMojo
118  * @see MojoParameter
119  * @see Basedir
120  */
121 public class MojoExtension extends MavenDIExtension implements ParameterResolver, BeforeEachCallback {
122 
123     protected static String pluginBasedir;
124     protected static String basedir;
125 
126     public static String getTestId() {
127         return context.getRequiredTestClass().getSimpleName() + "-"
128                 + context.getRequiredTestMethod().getName();
129     }
130 
131     public static String getBasedir() {
132         return requireNonNull(basedir != null ? basedir : MavenDIExtension.basedir);
133     }
134 
135     public static String getPluginBasedir() {
136         return requireNonNull(pluginBasedir);
137     }
138 
139     @Override
140     public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
141             throws ParameterResolutionException {
142         return parameterContext.isAnnotated(InjectMojo.class)
143                 || parameterContext.getDeclaringExecutable().isAnnotationPresent(InjectMojo.class);
144     }
145 
146     @Override
147     public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
148             throws ParameterResolutionException {
149         try {
150             Class<?> holder = parameterContext.getTarget().get().getClass();
151             PluginDescriptor descriptor = extensionContext
152                     .getStore(ExtensionContext.Namespace.GLOBAL)
153                     .get(PluginDescriptor.class, PluginDescriptor.class);
154             Model model =
155                     extensionContext.getStore(ExtensionContext.Namespace.GLOBAL).get(Model.class, Model.class);
156             InjectMojo parameterInjectMojo =
157                     parameterContext.getAnnotatedElement().getAnnotation(InjectMojo.class);
158             String goal;
159             if (parameterInjectMojo != null) {
160                 String pom = parameterInjectMojo.pom();
161                 if (pom != null && !pom.isEmpty()) {
162                     try (Reader r = openPomUrl(holder, pom, new Path[1])) {
163                         Model localModel = new MavenStaxReader().read(r);
164                         model = new MavenMerger().merge(localModel, model, false, null);
165                         model = new DefaultModelPathTranslator(new DefaultPathTranslator())
166                                 .alignToBaseDirectory(model, Paths.get(getBasedir()), null);
167                     }
168                 }
169                 goal = parameterInjectMojo.goal();
170             } else {
171                 InjectMojo methodInjectMojo = AnnotationSupport.findAnnotation(
172                                 parameterContext.getDeclaringExecutable(), InjectMojo.class)
173                         .orElse(null);
174                 if (methodInjectMojo != null) {
175                     goal = methodInjectMojo.goal();
176                 } else {
177                     goal = getGoalFromMojoImplementationClass(
178                             parameterContext.getParameter().getType());
179                 }
180             }
181 
182             Set<MojoParameter> mojoParameters = new LinkedHashSet<>();
183             for (AnnotatedElement ae :
184                     Arrays.asList(parameterContext.getDeclaringExecutable(), parameterContext.getAnnotatedElement())) {
185                 mojoParameters.addAll(AnnotationSupport.findRepeatableAnnotations(ae, MojoParameter.class));
186             }
187             String[] coord = mojoCoordinates(goal);
188 
189             XmlNode pluginConfiguration = model.getBuild().getPlugins().stream()
190                     .filter(p ->
191                             Objects.equals(p.getGroupId(), coord[0]) && Objects.equals(p.getArtifactId(), coord[1]))
192                     .map(ConfigurationContainer::getConfiguration)
193                     .findFirst()
194                     .orElseGet(() -> new XmlNodeImpl("config"));
195             List<XmlNode> children = mojoParameters.stream()
196                     .map(mp -> new XmlNodeImpl(mp.name(), mp.value()))
197                     .collect(Collectors.toList());
198             XmlNode config = new XmlNodeImpl("configuration", null, null, children, null);
199             pluginConfiguration = XmlNode.merge(config, pluginConfiguration);
200 
201             // load default config
202             // pluginkey = groupId : artifactId : version : goal
203             Mojo mojo = lookup(Mojo.class, coord[0] + ":" + coord[1] + ":" + coord[2] + ":" + coord[3]);
204             for (MojoDescriptor mojoDescriptor : descriptor.getMojos()) {
205                 if (Objects.equals(mojoDescriptor.getGoal(), coord[3])) {
206                     if (pluginConfiguration != null) {
207                         pluginConfiguration = finalizeConfig(pluginConfiguration, mojoDescriptor);
208                     }
209                 }
210             }
211 
212             Session session = getInjector().getInstance(Session.class);
213             Project project = getInjector().getInstance(Project.class);
214             MojoExecution mojoExecution = getInjector().getInstance(MojoExecution.class);
215             ExpressionEvaluator evaluator = new WrapEvaluator(
216                     getInjector(), new PluginParameterExpressionEvaluatorV4(session, project, mojoExecution));
217 
218             EnhancedComponentConfigurator configurator = new EnhancedComponentConfigurator();
219             configurator.configureComponent(
220                     mojo, new XmlPlexusConfiguration(pluginConfiguration), evaluator, null, null);
221             return mojo;
222         } catch (Exception e) {
223             throw new ParameterResolutionException("Unable to resolve mojo", e);
224         }
225     }
226 
227     /**
228      * The @Mojo annotation is only retained in the class file, not at runtime,
229      * so we need to actually read the class file with ASM to find the annotation and
230      * the goal.
231      */
232     private static String getGoalFromMojoImplementationClass(Class<?> cl) throws IOException {
233         return cl.getAnnotation(Named.class).value();
234     }
235 
236     @Override
237     @SuppressWarnings("checkstyle:MethodLength")
238     public void beforeEach(ExtensionContext context) throws Exception {
239         if (pluginBasedir == null) {
240             pluginBasedir = MavenDIExtension.getBasedir();
241         }
242         basedir = AnnotationSupport.findAnnotation(context.getElement().get(), Basedir.class)
243                 .map(Basedir::value)
244                 .orElse(pluginBasedir);
245         if (basedir != null) {
246             if (basedir.isEmpty()) {
247                 basedir = pluginBasedir + "/target/tests/"
248                         + context.getRequiredTestClass().getSimpleName() + "/"
249                         + context.getRequiredTestMethod().getName();
250             } else {
251                 basedir = basedir.replace("${basedir}", pluginBasedir);
252             }
253         }
254 
255         setContext(context);
256 
257         /*
258            binder.install(ProviderMethodsModule.forObject(context.getRequiredTestInstance()));
259            binder.requestInjection(context.getRequiredTestInstance());
260            binder.bind(Log.class).toInstance(new DefaultLog(LoggerFactory.getLogger("anonymous")));
261            binder.bind(ExtensionContext.class).toInstance(context);
262            // Load maven 4 api Services interfaces and try to bind them to the (possible) mock instances
263            // returned by the (possibly) mock InternalSession
264            try {
265                for (ClassPath.ClassInfo clazz :
266                        ClassPath.from(getClassLoader()).getAllClasses()) {
267                    if ("org.apache.maven.api.services".equals(clazz.getPackageName())) {
268                        Class<?> load = clazz.load();
269                        if (Service.class.isAssignableFrom(load)) {
270                            Class<Service> svc = (Class) load;
271                            binder.bind(svc).toProvider(() -> {
272                                try {
273                                    return getContainer()
274                                            .lookup(InternalSession.class)
275                                            .getService(svc);
276                                } catch (ComponentLookupException e) {
277                                    throw new RuntimeException("Unable to lookup service " + svc.getName());
278                                }
279                            });
280                        }
281                    }
282                }
283            } catch (Exception e) {
284                throw new RuntimeException("Unable to bind session services", e);
285            }
286 
287         */
288 
289         Path basedirPath = Paths.get(getBasedir());
290 
291         InjectMojo mojo = AnnotationSupport.findAnnotation(context.getElement().get(), InjectMojo.class)
292                 .orElse(null);
293         Model defaultModel = Model.newBuilder()
294                 .groupId("myGroupId")
295                 .artifactId("myArtifactId")
296                 .version("1.0-SNAPSHOT")
297                 .packaging("jar")
298                 .build(Build.newBuilder()
299                         .directory(basedirPath.resolve("target").toString())
300                         .outputDirectory(basedirPath.resolve("target/classes").toString())
301                         .sourceDirectory(basedirPath.resolve("src/main/java").toString())
302                         .testSourceDirectory(
303                                 basedirPath.resolve("src/test/java").toString())
304                         .testOutputDirectory(
305                                 basedirPath.resolve("target/test-classes").toString())
306                         .build())
307                 .build();
308         Path[] modelPath = new Path[] {null};
309         Model tmodel = null;
310         if (mojo != null) {
311             String pom = mojo.pom();
312             if (pom != null && !pom.isEmpty()) {
313                 try (Reader r = openPomUrl(context.getRequiredTestClass(), pom, modelPath)) {
314                     tmodel = new MavenStaxReader().read(r);
315                 }
316             } else {
317                 Path pomPath = basedirPath.resolve("pom.xml");
318                 if (Files.exists(pomPath)) {
319                     try (Reader r = Files.newBufferedReader(pomPath)) {
320                         tmodel = new MavenStaxReader().read(r);
321                         modelPath[0] = pomPath;
322                     }
323                 }
324             }
325         }
326         Model model;
327         if (tmodel == null) {
328             model = defaultModel;
329         } else {
330             model = new MavenMerger().merge(tmodel, defaultModel, false, null);
331         }
332         tmodel = new DefaultModelPathTranslator(new DefaultPathTranslator())
333                 .alignToBaseDirectory(tmodel, Paths.get(getBasedir()), null);
334         context.getStore(ExtensionContext.Namespace.GLOBAL).put(Model.class, tmodel);
335 
336         // mojo execution
337         // Map<Object, Object> map = getInjector().getContext().getContextData();
338         PluginDescriptor pluginDescriptor;
339         ClassLoader classLoader = context.getRequiredTestClass().getClassLoader();
340         try (InputStream is = requireNonNull(
341                         classLoader.getResourceAsStream(getPluginDescriptorLocation()),
342                         "Unable to find plugin descriptor: " + getPluginDescriptorLocation());
343                 Reader reader = new BufferedReader(new XmlStreamReader(is))) {
344             // new InterpolationFilterReader(reader, map, "${", "}");
345             pluginDescriptor = new PluginDescriptorStaxReader().read(reader);
346         }
347         context.getStore(ExtensionContext.Namespace.GLOBAL).put(PluginDescriptor.class, pluginDescriptor);
348         // for (ComponentDescriptor<?> desc : pluginDescriptor.getComponents()) {
349         //    getContainer().addComponentDescriptor(desc);
350         // }
351 
352         @SuppressWarnings({"unused", "MagicNumber"})
353         class Foo {
354 
355             @Provides
356             @Singleton
357             @Priority(-10)
358             private InternalSession createSession() {
359                 return SessionMock.getMockSession(getBasedir());
360             }
361 
362             @Provides
363             @Singleton
364             @Priority(-10)
365             private Project createProject(InternalSession s) {
366                 ProjectStub stub = new ProjectStub();
367                 if (!"pom".equals(model.getPackaging())) {
368                     ProducedArtifactStub artifact = new ProducedArtifactStub(
369                             model.getGroupId(), model.getArtifactId(), "", model.getVersion(), model.getPackaging());
370                     stub.setMainArtifact(artifact);
371                 }
372                 stub.setModel(model);
373                 stub.setBasedir(Paths.get(MojoExtension.getBasedir()));
374                 stub.setPomPath(modelPath[0]);
375                 s.getService(ArtifactManager.class).setPath(stub.getPomArtifact(), modelPath[0]);
376                 return stub;
377             }
378 
379             @Provides
380             @Singleton
381             @Priority(-10)
382             private MojoExecution createMojoExecution() {
383                 MojoExecutionStub mes = new MojoExecutionStub("executionId", null);
384                 if (mojo != null) {
385                     String goal = mojo.goal();
386                     int idx = goal.lastIndexOf(':');
387                     if (idx >= 0) {
388                         goal = goal.substring(idx + 1);
389                     }
390                     mes.setGoal(goal);
391                     for (MojoDescriptor md : pluginDescriptor.getMojos()) {
392                         if (goal.equals(md.getGoal())) {
393                             mes.setDescriptor(md);
394                         }
395                     }
396                     requireNonNull(mes.getDescriptor());
397                 }
398                 PluginStub plugin = new PluginStub();
399                 plugin.setDescriptor(pluginDescriptor);
400                 mes.setPlugin(plugin);
401                 return mes;
402             }
403 
404             @Provides
405             @Singleton
406             @Priority(-10)
407             private Log createLog() {
408                 return new DefaultLog(LoggerFactory.getLogger("anonymous"));
409             }
410 
411             @Provides
412             static RepositorySystemSupplier newRepositorySystemSupplier() {
413                 return new RepositorySystemSupplier();
414             }
415 
416             @Provides
417             static RepositorySystem newRepositorySystem(RepositorySystemSupplier repositorySystemSupplier) {
418                 return repositorySystemSupplier.getRepositorySystem();
419             }
420 
421             @Provides
422             @Priority(10)
423             static RepositoryFactory newRepositoryFactory(Session session) {
424                 return session.getService(RepositoryFactory.class);
425             }
426 
427             @Provides
428             @Priority(10)
429             static VersionParser newVersionParser(Session session) {
430                 return session.getService(VersionParser.class);
431             }
432 
433             @Provides
434             @Priority(10)
435             static LocalRepositoryManager newLocalRepositoryManager(Session session) {
436                 return session.getService(LocalRepositoryManager.class);
437             }
438 
439             @Provides
440             @Priority(10)
441             static ArtifactInstaller newArtifactInstaller(Session session) {
442                 return session.getService(ArtifactInstaller.class);
443             }
444 
445             @Provides
446             @Priority(10)
447             static ArtifactDeployer newArtifactDeployer(Session session) {
448                 return session.getService(ArtifactDeployer.class);
449             }
450 
451             @Provides
452             @Priority(10)
453             static ArtifactManager newArtifactManager(Session session) {
454                 return session.getService(ArtifactManager.class);
455             }
456 
457             @Provides
458             @Priority(10)
459             static ProjectManager newProjectManager(Session session) {
460                 return session.getService(ProjectManager.class);
461             }
462 
463             @Provides
464             @Priority(10)
465             static ArtifactFactory newArtifactFactory(Session session) {
466                 return session.getService(ArtifactFactory.class);
467             }
468 
469             @Provides
470             @Priority(10)
471             static ProjectBuilder newProjectBuilder(Session session) {
472                 return session.getService(ProjectBuilder.class);
473             }
474 
475             @Provides
476             @Priority(10)
477             static ModelXmlFactory newModelXmlFactory(Session session) {
478                 return session.getService(ModelXmlFactory.class);
479             }
480         }
481 
482         getInjector().bindInstance(Foo.class, new Foo());
483 
484         getInjector().injectInstance(context.getRequiredTestInstance());
485 
486         //        SessionScope sessionScope = getInjector().getInstance(SessionScope.class);
487         //        sessionScope.enter();
488         //        sessionScope.seed(Session.class, s);
489         //        sessionScope.seed(InternalSession.class, s);
490 
491         //        MojoExecutionScope mojoExecutionScope = getInjector().getInstance(MojoExecutionScope.class);
492         //        mojoExecutionScope.enter();
493         //        mojoExecutionScope.seed(Project.class, p);
494         //        mojoExecutionScope.seed(MojoExecution.class, me);
495     }
496 
497     private Reader openPomUrl(Class<?> holder, String pom, Path[] modelPath) throws IOException {
498         if (pom.startsWith("file:")) {
499             Path path = Paths.get(getBasedir()).resolve(pom.substring("file:".length()));
500             modelPath[0] = path;
501             return Files.newBufferedReader(path);
502         } else if (pom.startsWith("classpath:")) {
503             URL url = holder.getResource(pom.substring("classpath:".length()));
504             if (url == null) {
505                 throw new IllegalStateException("Unable to find pom on classpath: " + pom);
506             }
507             return new XmlStreamReader(url.openStream());
508         } else if (pom.contains("<project>")) {
509             return new StringReader(pom);
510         } else {
511             Path path = Paths.get(getBasedir()).resolve(pom);
512             modelPath[0] = path;
513             return Files.newBufferedReader(path);
514         }
515     }
516 
517     protected String getPluginDescriptorLocation() {
518         return "META-INF/maven/plugin.xml";
519     }
520 
521     protected String[] mojoCoordinates(String goal) throws Exception {
522         if (goal.matches(".*:.*:.*:.*")) {
523             return goal.split(":");
524         } else {
525             Path pluginPom = Paths.get(getPluginBasedir(), "pom.xml");
526             Xpp3Dom pluginPomDom = Xpp3DomBuilder.build(Files.newBufferedReader(pluginPom));
527             String artifactId = pluginPomDom.getChild("artifactId").getValue();
528             String groupId = resolveFromRootThenParent(pluginPomDom, "groupId");
529             String version = resolveFromRootThenParent(pluginPomDom, "version");
530             return new String[] {groupId, artifactId, version, goal};
531         }
532     }
533 
534     private XmlNode finalizeConfig(XmlNode config, MojoDescriptor mojoDescriptor) {
535         List<XmlNode> children = new ArrayList<>();
536         if (mojoDescriptor != null && mojoDescriptor.getParameters() != null) {
537             XmlNode defaultConfiguration;
538             defaultConfiguration = MojoDescriptorCreator.convert(mojoDescriptor);
539             for (Parameter parameter : mojoDescriptor.getParameters()) {
540                 XmlNode parameterConfiguration = config.getChild(parameter.getName());
541                 if (parameterConfiguration == null) {
542                     parameterConfiguration = config.getChild(parameter.getAlias());
543                 }
544                 XmlNode parameterDefaults = defaultConfiguration.getChild(parameter.getName());
545                 parameterConfiguration = XmlNode.merge(parameterConfiguration, parameterDefaults, Boolean.TRUE);
546                 if (parameterConfiguration != null) {
547                     Map<String, String> attributes = new HashMap<>(parameterConfiguration.getAttributes());
548                     // if (isEmpty(parameterConfiguration.getAttribute("implementation"))
549                     //         && !isEmpty(parameter.getImplementation())) {
550                     //     attributes.put("implementation", parameter.getImplementation());
551                     // }
552                     parameterConfiguration = new XmlNodeImpl(
553                             parameter.getName(),
554                             parameterConfiguration.getValue(),
555                             attributes,
556                             parameterConfiguration.getChildren(),
557                             parameterConfiguration.getInputLocation());
558 
559                     children.add(parameterConfiguration);
560                 }
561             }
562         }
563         return new XmlNodeImpl("configuration", null, null, children, null);
564     }
565 
566     private boolean isEmpty(String str) {
567         return str == null || str.isEmpty();
568     }
569 
570     private static Optional<Xpp3Dom> child(Xpp3Dom element, String name) {
571         return Optional.ofNullable(element.getChild(name));
572     }
573 
574     private static Stream<Xpp3Dom> children(Xpp3Dom element) {
575         return Stream.of(element.getChildren());
576     }
577 
578     public static XmlNode extractPluginConfiguration(String artifactId, Xpp3Dom pomDom) throws Exception {
579         Xpp3Dom pluginConfigurationElement = child(pomDom, "build")
580                 .flatMap(buildElement -> child(buildElement, "plugins"))
581                 .map(MojoExtension::children)
582                 .orElseGet(Stream::empty)
583                 .filter(e -> e.getChild("artifactId").getValue().equals(artifactId))
584                 .findFirst()
585                 .flatMap(buildElement -> child(buildElement, "configuration"))
586                 .orElse(Xpp3DomBuilder.build(new StringReader("<configuration/>")));
587         return pluginConfigurationElement.getDom();
588     }
589 
590     /**
591      * sometimes the parent element might contain the correct value so generalize that access
592      *
593      * TODO find out where this is probably done elsewhere
594      */
595     private static String resolveFromRootThenParent(Xpp3Dom pluginPomDom, String element) throws Exception {
596         return Optional.ofNullable(child(pluginPomDom, element).orElseGet(() -> child(pluginPomDom, "parent")
597                         .flatMap(e -> child(e, element))
598                         .orElse(null)))
599                 .map(Xpp3Dom::getValue)
600                 .orElseThrow(() -> new Exception("unable to determine " + element));
601     }
602 
603     /**
604      * Convenience method to obtain the value of a variable on a mojo that might not have a getter.
605      * <br>
606      * NOTE: the caller is responsible for casting to what the desired type is.
607      */
608     public static Object getVariableValueFromObject(Object object, String variable) throws IllegalAccessException {
609         Field field = ReflectionUtils.getFieldByNameIncludingSuperclasses(variable, object.getClass());
610         field.setAccessible(true);
611         return field.get(object);
612     }
613 
614     /**
615      * Convenience method to obtain all variables and values from the mojo (including its superclasses)
616      * <br>
617      * Note: the values in the map are of type Object so the caller is responsible for casting to desired types.
618      */
619     public static Map<String, Object> getVariablesAndValuesFromObject(Object object) throws IllegalAccessException {
620         return getVariablesAndValuesFromObject(object.getClass(), object);
621     }
622 
623     /**
624      * Convenience method to obtain all variables and values from the mojo (including its superclasses)
625      * <br>
626      * Note: the values in the map are of type Object so the caller is responsible for casting to desired types.
627      *
628      * @return map of variable names and values
629      */
630     public static Map<String, Object> getVariablesAndValuesFromObject(Class<?> clazz, Object object)
631             throws IllegalAccessException {
632         Map<String, Object> map = new HashMap<>();
633         Field[] fields = clazz.getDeclaredFields();
634         AccessibleObject.setAccessible(fields, true);
635         for (Field field : fields) {
636             map.put(field.getName(), field.get(object));
637         }
638         Class<?> superclass = clazz.getSuperclass();
639         if (!Object.class.equals(superclass)) {
640             map.putAll(getVariablesAndValuesFromObject(superclass, object));
641         }
642         return map;
643     }
644 
645     /**
646      * Convenience method to set values to variables in objects that don't have setters
647      */
648     public static void setVariableValueToObject(Object object, String variable, Object value)
649             throws IllegalAccessException {
650         Field field = ReflectionUtils.getFieldByNameIncludingSuperclasses(variable, object.getClass());
651         requireNonNull(field, "Field " + variable + " not found");
652         field.setAccessible(true);
653         field.set(object, value);
654     }
655 
656     static class WrapEvaluator implements TypeAwareExpressionEvaluator {
657 
658         private final Injector injector;
659         private final TypeAwareExpressionEvaluator evaluator;
660 
661         WrapEvaluator(Injector injector, TypeAwareExpressionEvaluator evaluator) {
662             this.injector = injector;
663             this.evaluator = evaluator;
664         }
665 
666         @Override
667         public Object evaluate(String expression) throws ExpressionEvaluationException {
668             return evaluate(expression, null);
669         }
670 
671         @Override
672         public Object evaluate(String expression, Class<?> type) throws ExpressionEvaluationException {
673             Object value = evaluator.evaluate(expression, type);
674             if (value == null) {
675                 String expr = stripTokens(expression);
676                 if (expr != null) {
677                     try {
678                         value = injector.getInstance(Key.of(type, expr));
679                     } catch (DIException e) {
680                         // nothing
681                     }
682                 }
683             }
684             return value;
685         }
686 
687         private String stripTokens(String expr) {
688             if (expr.startsWith("${") && expr.endsWith("}")) {
689                 return expr.substring(2, expr.length() - 1);
690             }
691             return null;
692         }
693 
694         @Override
695         public File alignToBaseDirectory(File path) {
696             return evaluator.alignToBaseDirectory(path);
697         }
698     }
699 
700     /*
701     private Scope getScopeInstanceOrNull(final Injector injector, final Binding<?> binding) {
702         return binding.acceptScopingVisitor(new DefaultBindingScopingVisitor<Scope>() {
703 
704             @Override
705             public Scope visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
706                 throw new RuntimeException(String.format(
707                         "I don't know how to handle the scopeAnnotation: %s", scopeAnnotation.getCanonicalName()));
708             }
709 
710             @Override
711             public Scope visitNoScoping() {
712                 if (binding instanceof LinkedKeyBinding) {
713                     Binding<?> childBinding = injector.getBinding(((LinkedKeyBinding) binding).getLinkedKey());
714                     return getScopeInstanceOrNull(injector, childBinding);
715                 }
716                 return null;
717             }
718 
719             @Override
720             public Scope visitEagerSingleton() {
721                 return Scopes.SINGLETON;
722             }
723 
724             public Scope visitScope(Scope scope) {
725                 return scope;
726             }
727         });
728     }*/
729 
730 }