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