View Javadoc
1   /*
2    *    Copyright 2010-2025 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  /*
17   * Licensed to the Apache Software Foundation (ASF) under one
18   * or more contributor license agreements.  See the NOTICE file
19   * distributed with this work for additional information
20   * regarding copyright ownership.  The ASF licenses this file
21   * to you under the Apache License, Version 2.0 (the
22   * "License"); you may not use this file except in compliance
23   * with the License.  You may obtain a copy of the License at
24   *
25   *   http://www.apache.org/licenses/LICENSE-2.0
26   *
27   * Unless required by applicable law or agreed to in writing,
28   * software distributed under the License is distributed on an
29   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
30   * KIND, either express or implied.  See the License for the
31   * specific language governing permissions and limitations
32   * under the License.
33   */
34  package org.mybatis.maven.testing;
35  
36  import com.google.inject.Module;
37  
38  import java.io.BufferedReader;
39  import java.io.File;
40  import java.io.IOException;
41  import java.io.InputStream;
42  import java.io.Reader;
43  import java.lang.reflect.AccessibleObject;
44  import java.lang.reflect.Field;
45  import java.net.MalformedURLException;
46  import java.net.URL;
47  import java.nio.file.Files;
48  import java.nio.file.Path;
49  import java.util.ArrayList;
50  import java.util.Arrays;
51  import java.util.HashMap;
52  import java.util.List;
53  import java.util.Map;
54  import java.util.Properties;
55  
56  import org.apache.maven.artifact.Artifact;
57  import org.apache.maven.artifact.DefaultArtifact;
58  import org.apache.maven.artifact.handler.DefaultArtifactHandler;
59  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
60  import org.apache.maven.execution.DefaultMavenExecutionRequest;
61  import org.apache.maven.execution.DefaultMavenExecutionResult;
62  import org.apache.maven.execution.MavenExecutionRequest;
63  import org.apache.maven.execution.MavenExecutionResult;
64  import org.apache.maven.execution.MavenSession;
65  import org.apache.maven.lifecycle.internal.MojoDescriptorCreator;
66  import org.apache.maven.model.Plugin;
67  import org.apache.maven.plugin.Mojo;
68  import org.apache.maven.plugin.MojoExecution;
69  import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
70  import org.apache.maven.plugin.descriptor.MojoDescriptor;
71  import org.apache.maven.plugin.descriptor.Parameter;
72  import org.apache.maven.plugin.descriptor.PluginDescriptor;
73  import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
74  import org.apache.maven.project.MavenProject;
75  import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
76  import org.codehaus.plexus.ContainerConfiguration;
77  import org.codehaus.plexus.DefaultContainerConfiguration;
78  import org.codehaus.plexus.DefaultPlexusContainer;
79  import org.codehaus.plexus.PlexusConstants;
80  import org.codehaus.plexus.PlexusContainer;
81  import org.codehaus.plexus.PlexusContainerException;
82  import org.codehaus.plexus.PlexusTestCase;
83  import org.codehaus.plexus.classworlds.ClassWorld;
84  import org.codehaus.plexus.component.configurator.ComponentConfigurator;
85  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
86  import org.codehaus.plexus.component.repository.ComponentDescriptor;
87  import org.codehaus.plexus.configuration.PlexusConfiguration;
88  import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
89  import org.codehaus.plexus.context.Context;
90  import org.codehaus.plexus.util.InterpolationFilterReader;
91  import org.codehaus.plexus.util.ReaderFactory;
92  import org.codehaus.plexus.util.ReflectionUtils;
93  import org.codehaus.plexus.util.StringUtils;
94  import org.codehaus.plexus.util.xml.XmlStreamReader;
95  import org.codehaus.plexus.util.xml.Xpp3Dom;
96  import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
97  
98  /**
99   * Mybatis: Borrowed from 'https://github.com/apache/maven-plugin-testing/blob/master/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/AbstractMojoTestCase.java'
100  * Reason: Too restrictive to use directly for junit 5.
101  * Changes: Imports added, method access expanded, removed some left over junk code
102  * Git: From release 4.0.0-alpha-2
103  */
104 
105 /**
106  * TODO: add a way to use the plugin POM for the lookup so that the user doesn't have to provide the a:g:v:goal as the
107  * role hint for the mojo lookup. TODO: standardize the execution of the mojo and looking at the results, but could
108  * simply have a template method for verifying the state of the mojo post execution TODO: need a way to look at the
109  * state of the mojo without adding getters, this could be where we finally specify the expressions which extract values
110  * from the mojo. TODO: create a standard directory structure for picking up POMs to make this even easier, we really
111  * just need a testing descriptor and make this entirely declarative!
112  *
113  * @author jesse
114  */
115 public abstract class AbstractMojoTestCase extends PlexusTestCase {
116   private static final DefaultArtifactVersion MAVEN_VERSION;
117 
118   static {
119     DefaultArtifactVersion version = null;
120     String path = "/META-INF/maven/org.apache.maven/maven-core/pom.properties";
121 
122     try (InputStream is = AbstractMojoTestCase.class.getResourceAsStream(path)) {
123       Properties properties = new Properties();
124       if (is != null) {
125         properties.load(is);
126       }
127       String property = properties.getProperty("version");
128       if (property != null) {
129         version = new DefaultArtifactVersion(property);
130       }
131     } catch (IOException e) {
132       // odd, where did this come from
133     }
134     MAVEN_VERSION = version;
135   }
136 
137   private ComponentConfigurator configurator;
138 
139   private PlexusContainer container;
140 
141   private Map<String, MojoDescriptor> mojoDescriptors;
142 
143   /*
144    * for the harness I think we have decided against going the route of using the maven project builder. instead I think
145    * we are going to try and make an instance of the localrespository and assign that to either the project stub or into
146    * the mojo directly with injection...not sure yet though.
147    */
148   @Override
149   public void setUp() throws Exception {
150     assertTrue("Maven 3.2.4 or better is required",
151         MAVEN_VERSION == null || new DefaultArtifactVersion("3.2.3").compareTo(MAVEN_VERSION) < 0);
152 
153     configurator = getContainer().lookup(ComponentConfigurator.class, "basic");
154     Context context = container.getContext();
155     Map<Object, Object> map = context.getContextData();
156 
157     try (InputStream is = getClass().getResourceAsStream("/" + getPluginDescriptorLocation());
158         Reader reader = new BufferedReader(new XmlStreamReader(is));
159         InterpolationFilterReader interpolationReader = new InterpolationFilterReader(reader, map, "${", "}")) {
160 
161       PluginDescriptor pluginDescriptor = new PluginDescriptorBuilder().build(interpolationReader);
162 
163       Artifact artifact = new DefaultArtifact(pluginDescriptor.getGroupId(), pluginDescriptor.getArtifactId(),
164           pluginDescriptor.getVersion(), null, "jar", null, new DefaultArtifactHandler("jar"));
165 
166       artifact.setFile(getPluginArtifactFile());
167       pluginDescriptor.setPluginArtifact(artifact);
168       pluginDescriptor.setArtifacts(Arrays.asList(artifact));
169 
170       for (ComponentDescriptor<?> desc : pluginDescriptor.getComponents()) {
171         getContainer().addComponentDescriptor(desc);
172       }
173 
174       mojoDescriptors = new HashMap<>();
175       for (MojoDescriptor mojoDescriptor : pluginDescriptor.getMojos()) {
176         mojoDescriptors.put(mojoDescriptor.getGoal(), mojoDescriptor);
177       }
178     }
179   }
180 
181   /**
182    * Returns best-effort plugin artifact file.
183    * <p>
184    * First, attempts to determine parent directory of META-INF directory holding the plugin descriptor. If META-INF
185    * parent directory cannot be determined, falls back to test basedir.
186    */
187   private File getPluginArtifactFile() throws IOException {
188     final String pluginDescriptorLocation = getPluginDescriptorLocation();
189     final URL resource = getClass().getResource("/" + pluginDescriptorLocation);
190 
191     Path file = null;
192 
193     // attempt to resolve relative to META-INF/maven/plugin.xml first
194     if (resource != null) {
195       if ("file".equalsIgnoreCase(resource.getProtocol())) {
196         String path = resource.getPath();
197         if (path.endsWith(pluginDescriptorLocation)) {
198           file = Path.of(path.substring(0, path.length() - pluginDescriptorLocation.length()).replace(":", ""));
199         }
200       } else if ("jar".equalsIgnoreCase(resource.getProtocol())) {
201         // TODO is there a helper for this somewhere?
202         try {
203           URL jarfile = new URL(resource.getPath());
204           if ("file".equalsIgnoreCase(jarfile.getProtocol())) {
205             String path = jarfile.getPath();
206             if (path.endsWith(pluginDescriptorLocation)) {
207               file = Path.of(path.substring(0, path.length() - pluginDescriptorLocation.length() - 2).replace(":", ""));
208             }
209           }
210         } catch (MalformedURLException e) {
211           // not jar:file:/ URL, too bad
212         }
213       }
214     }
215 
216     // fallback to test project basedir if couldn't resolve relative to META-INF/maven/plugin.xml
217     if (file == null || Files.notExists(file)) {
218       file = Path.of(getBasedir());
219     }
220 
221     return file.toFile().getCanonicalFile();
222   }
223 
224   protected InputStream getPublicDescriptorStream() throws Exception {
225     return Files.newInputStream(Path.of(getPluginDescriptorPath()));
226   }
227 
228   protected String getPluginDescriptorPath() {
229     return getBasedir() + "/target/classes/META-INF/maven/plugin.xml";
230   }
231 
232   protected String getPluginDescriptorLocation() {
233     return "META-INF/maven/plugin.xml";
234   }
235 
236   @Override
237   protected void setupContainer() {
238     ContainerConfiguration cc = setupContainerConfiguration();
239     try {
240       List<Module> modules = new ArrayList<>();
241       addGuiceModules(modules);
242       container = new DefaultPlexusContainer(cc, modules.toArray(new Module[0]));
243     } catch (PlexusContainerException e) {
244       e.printStackTrace();
245       fail("Failed to create plexus container.");
246     }
247   }
248 
249   /**
250    * @since 3.0.0
251    */
252   protected void addGuiceModules(List<Module> modules) {
253     // no custom guice modules by default
254   }
255 
256   protected ContainerConfiguration setupContainerConfiguration() {
257     ClassWorld classWorld = new ClassWorld("plexus.core", Thread.currentThread().getContextClassLoader());
258 
259     ContainerConfiguration cc = new DefaultContainerConfiguration().setClassWorld(classWorld)
260         .setClassPathScanning(PlexusConstants.SCANNING_INDEX).setAutoWiring(true).setName("maven");
261 
262     return cc;
263   }
264 
265   @Override
266   protected PlexusContainer getContainer() {
267     if (container == null) {
268       setupContainer();
269     }
270 
271     return container;
272   }
273 
274   /**
275    * Lookup the mojo leveraging the subproject pom
276    *
277    * @param goal
278    * @param pluginPom
279    *
280    * @return a Mojo instance
281    *
282    * @throws Exception
283    */
284   protected <T extends Mojo> T lookupMojo(String goal, String pluginPom) throws Exception {
285     return lookupMojo(goal, Path.of(pluginPom));
286   }
287 
288   /**
289    * Lookup an empty mojo
290    *
291    * @param goal
292    * @param pluginPom
293    *
294    * @return a Mojo instance
295    *
296    * @throws Exception
297    */
298   protected <T extends Mojo> T lookupEmptyMojo(String goal, String pluginPom) throws Exception {
299     return lookupEmptyMojo(goal, Path.of(pluginPom).toFile());
300   }
301 
302   /**
303    * Lookup the mojo leveraging the actual subprojects pom
304    *
305    * @param goal
306    * @param pom
307    *
308    * @return a Mojo instance
309    *
310    * @throws Exception
311    */
312   public <T extends Mojo> T lookupMojo(String goal, Path pom) throws Exception {
313     Path pluginPom = Path.of(getBasedir(), "pom.xml");
314 
315     Xpp3Dom pluginPomDom = Xpp3DomBuilder.build(ReaderFactory.newXmlReader(pluginPom.toFile()));
316 
317     String artifactId = pluginPomDom.getChild("artifactId").getValue();
318 
319     String groupId = resolveFromRootThenParent(pluginPomDom, "groupId");
320 
321     String version = resolveFromRootThenParent(pluginPomDom, "version");
322 
323     PlexusConfiguration pluginConfiguration = extractPluginConfiguration(artifactId, pom);
324 
325     return lookupMojo(groupId, artifactId, version, goal, pluginConfiguration);
326   }
327 
328   /**
329    * Lookup the mojo leveraging the actual subprojects pom
330    *
331    * @param goal
332    * @param pom
333    *
334    * @return a Mojo instance
335    *
336    * @throws Exception
337    */
338   protected <T extends Mojo> T lookupEmptyMojo(String goal, File pom) throws Exception {
339     Path pluginPom = Path.of(getBasedir(), "pom.xml");
340 
341     Xpp3Dom pluginPomDom = Xpp3DomBuilder.build(ReaderFactory.newXmlReader(pluginPom.toFile()));
342 
343     String artifactId = pluginPomDom.getChild("artifactId").getValue();
344 
345     String groupId = resolveFromRootThenParent(pluginPomDom, "groupId");
346 
347     String version = resolveFromRootThenParent(pluginPomDom, "version");
348 
349     return lookupMojo(groupId, artifactId, version, goal, null);
350   }
351 
352   /**
353    * lookup the mojo while we have all of the relavent information
354    *
355    * @param groupId
356    * @param artifactId
357    * @param version
358    * @param goal
359    * @param pluginConfiguration
360    *
361    * @return a Mojo instance
362    *
363    * @throws Exception
364    */
365   protected <T extends Mojo> T lookupMojo(String groupId, String artifactId, String version, String goal,
366       PlexusConfiguration pluginConfiguration) throws Exception {
367     validateContainerStatus();
368 
369     // pluginkey = groupId : artifactId : version : goal
370 
371     T mojo = (T) lookup(Mojo.class, groupId + ":" + artifactId + ":" + version + ":" + goal);
372 
373     if (pluginConfiguration != null) {
374       ExpressionEvaluator evaluator = new ResolverExpressionEvaluatorStub();
375 
376       configurator.configureComponent(mojo, pluginConfiguration, evaluator, getContainer().getContainerRealm());
377     }
378 
379     return mojo;
380   }
381 
382   /**
383    * @param project
384    * @param goal
385    *
386    * @return
387    *
388    * @throws Exception
389    *
390    * @since 2.0
391    */
392   protected <T extends Mojo> T lookupConfiguredMojo(MavenProject project, String goal) throws Exception {
393     return lookupConfiguredMojo(newMavenSession(project), newMojoExecution(goal));
394   }
395 
396   /**
397    * @param session
398    * @param execution
399    *
400    * @return
401    *
402    * @throws Exception
403    *
404    * @since 2.0
405    */
406   protected <T extends Mojo> T lookupConfiguredMojo(MavenSession session, MojoExecution execution) throws Exception {
407     MavenProject project = session.getCurrentProject();
408     MojoDescriptor mojoDescriptor = execution.getMojoDescriptor();
409 
410     T mojo = (T) lookup(mojoDescriptor.getRole(), mojoDescriptor.getRoleHint());
411 
412     ExpressionEvaluator evaluator = new PluginParameterExpressionEvaluator(session, execution);
413 
414     Xpp3Dom configuration = null;
415     Plugin plugin = project.getPlugin(mojoDescriptor.getPluginDescriptor().getPluginLookupKey());
416     if (plugin != null) {
417       configuration = (Xpp3Dom) plugin.getConfiguration();
418     }
419     if (configuration == null) {
420       configuration = new Xpp3Dom("configuration");
421     }
422     configuration = Xpp3Dom.mergeXpp3Dom(configuration, execution.getConfiguration());
423 
424     PlexusConfiguration pluginConfiguration = new XmlPlexusConfiguration(configuration);
425 
426     if (mojoDescriptor.getComponentConfigurator() != null) {
427       configurator = getContainer().lookup(ComponentConfigurator.class, mojoDescriptor.getComponentConfigurator());
428     }
429 
430     configurator.configureComponent(mojo, pluginConfiguration, evaluator, getContainer().getContainerRealm());
431 
432     return mojo;
433   }
434 
435   /**
436    * @param project
437    *
438    * @return
439    *
440    * @since 2.0
441    */
442   protected MavenSession newMavenSession(MavenProject project) {
443     MavenExecutionRequest request = new DefaultMavenExecutionRequest();
444     MavenExecutionResult result = new DefaultMavenExecutionResult();
445 
446     MavenSession session = new MavenSession(container, MavenRepositorySystemUtils.newSession(), request, result);
447     session.setCurrentProject(project);
448     session.setProjects(Arrays.asList(project));
449     return session;
450   }
451 
452   /**
453    * @param goal
454    *
455    * @return
456    *
457    * @since 2.0
458    */
459   protected MojoExecution newMojoExecution(String goal) {
460     MojoDescriptor mojoDescriptor = mojoDescriptors.get(goal);
461     assertNotNull(String.format("The MojoDescriptor for the goal %s cannot be null.", goal), mojoDescriptor);
462     MojoExecution execution = new MojoExecution(mojoDescriptor);
463     finalizeMojoConfiguration(execution);
464     return execution;
465   }
466 
467   // copy&paste from o.a.m.l.i.DefaultLifecycleExecutionPlanCalculator.finalizeMojoConfiguration(MojoExecution)
468   private void finalizeMojoConfiguration(MojoExecution mojoExecution) {
469     MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
470 
471     Xpp3Dom executionConfiguration = mojoExecution.getConfiguration();
472     if (executionConfiguration == null) {
473       executionConfiguration = new Xpp3Dom("configuration");
474     }
475 
476     Xpp3Dom defaultConfiguration = new Xpp3Dom(MojoDescriptorCreator.convert(mojoDescriptor));
477 
478     Xpp3Dom finalConfiguration = new Xpp3Dom("configuration");
479 
480     if (mojoDescriptor.getParameters() != null) {
481       for (Parameter parameter : mojoDescriptor.getParameters()) {
482         Xpp3Dom parameterConfiguration = executionConfiguration.getChild(parameter.getName());
483 
484         if (parameterConfiguration == null) {
485           parameterConfiguration = executionConfiguration.getChild(parameter.getAlias());
486         }
487 
488         Xpp3Dom parameterDefaults = defaultConfiguration.getChild(parameter.getName());
489 
490         parameterConfiguration = Xpp3Dom.mergeXpp3Dom(parameterConfiguration, parameterDefaults, Boolean.TRUE);
491 
492         if (parameterConfiguration != null) {
493           parameterConfiguration = new Xpp3Dom(parameterConfiguration, parameter.getName());
494 
495           if (StringUtils.isEmpty(parameterConfiguration.getAttribute("implementation"))
496               && StringUtils.isNotEmpty(parameter.getImplementation())) {
497             parameterConfiguration.setAttribute("implementation", parameter.getImplementation());
498           }
499 
500           finalConfiguration.addChild(parameterConfiguration);
501         }
502       }
503     }
504 
505     mojoExecution.setConfiguration(finalConfiguration);
506   }
507 
508   /**
509    * @param artifactId
510    * @param pom
511    *
512    * @return the plexus configuration
513    *
514    * @throws Exception
515    */
516   protected PlexusConfiguration extractPluginConfiguration(String artifactId, Path pom) throws Exception {
517 
518     try (Reader reader = ReaderFactory.newXmlReader(pom.toFile())) {
519       Xpp3Dom pomDom = Xpp3DomBuilder.build(reader);
520       return extractPluginConfiguration(artifactId, pomDom);
521     }
522   }
523 
524   /**
525    * @param artifactId
526    * @param pomDom
527    *
528    * @return the plexus configuration
529    *
530    * @throws Exception
531    */
532   protected PlexusConfiguration extractPluginConfiguration(String artifactId, Xpp3Dom pomDom) throws Exception {
533     Xpp3Dom pluginConfigurationElement = null;
534 
535     Xpp3Dom buildElement = pomDom.getChild("build");
536     if (buildElement != null) {
537       Xpp3Dom pluginsRootElement = buildElement.getChild("plugins");
538 
539       if (pluginsRootElement != null) {
540         Xpp3Dom[] pluginElements = pluginsRootElement.getChildren();
541 
542         for (Xpp3Dom pluginElement : pluginElements) {
543           String pluginElementArtifactId = pluginElement.getChild("artifactId").getValue();
544 
545           if (pluginElementArtifactId.equals(artifactId)) {
546             pluginConfigurationElement = pluginElement.getChild("configuration");
547 
548             break;
549           }
550         }
551 
552         if (pluginConfigurationElement == null) {
553           throw new ConfigurationException(
554               "Cannot find a configuration element for a plugin with an " + "artifactId of " + artifactId + ".");
555         }
556       }
557     }
558 
559     if (pluginConfigurationElement == null) {
560       throw new ConfigurationException(
561           "Cannot find a configuration element for a plugin with an artifactId of " + artifactId + ".");
562     }
563 
564     return new XmlPlexusConfiguration(pluginConfigurationElement);
565   }
566 
567   /**
568    * Configure the mojo
569    *
570    * @param mojo
571    * @param artifactId
572    * @param pom
573    *
574    * @return a Mojo instance
575    *
576    * @throws Exception
577    */
578   protected <T extends Mojo> T configureMojo(T mojo, String artifactId, Path pom) throws Exception {
579     validateContainerStatus();
580 
581     PlexusConfiguration pluginConfiguration = extractPluginConfiguration(artifactId, pom);
582 
583     ExpressionEvaluator evaluator = new ResolverExpressionEvaluatorStub();
584 
585     configurator.configureComponent(mojo, pluginConfiguration, evaluator, getContainer().getContainerRealm());
586 
587     return mojo;
588   }
589 
590   /**
591    * Configure the mojo with the given plexus configuration
592    *
593    * @param mojo
594    * @param pluginConfiguration
595    *
596    * @return a Mojo instance
597    *
598    * @throws Exception
599    */
600   protected <T extends Mojo> T configureMojo(T mojo, PlexusConfiguration pluginConfiguration) throws Exception {
601     validateContainerStatus();
602 
603     ExpressionEvaluator evaluator = new ResolverExpressionEvaluatorStub();
604 
605     configurator.configureComponent(mojo, pluginConfiguration, evaluator, getContainer().getContainerRealm());
606 
607     return mojo;
608   }
609 
610   /**
611    * Convenience method to obtain the value of a variable on a mojo that might not have a getter. NOTE: the caller is
612    * responsible for casting to to what the desired type is.
613    *
614    * @param object
615    * @param variable
616    *
617    * @return object value of variable
618    *
619    * @throws IllegalArgumentException
620    */
621   protected <T> T getVariableValueFromObject(Object object, String variable) throws IllegalAccessException {
622     Field field = ReflectionUtils.getFieldByNameIncludingSuperclasses(variable, object.getClass());
623 
624     field.setAccessible(true);
625 
626     return (T) field.get(object);
627   }
628 
629   /**
630    * Convenience method to obtain all variables and values from the mojo (including its superclasses) Note: the values
631    * in the map are of type Object so the caller is responsible for casting to desired types.
632    *
633    * @param object
634    *
635    * @return map of variable names and values
636    */
637   protected Map<String, Object> getVariablesAndValuesFromObject(Object object) throws IllegalAccessException {
638     return getVariablesAndValuesFromObject(object.getClass(), object);
639   }
640 
641   /**
642    * Convenience method to obtain all variables and values from the mojo (including its superclasses) Note: the values
643    * in the map are of type Object so the caller is responsible for casting to desired types.
644    *
645    * @param clazz
646    * @param object
647    *
648    * @return map of variable names and values
649    */
650   protected Map<String, Object> getVariablesAndValuesFromObject(Class<?> clazz, Object object)
651       throws IllegalAccessException {
652     Map<String, Object> map = new HashMap<>();
653 
654     Field[] fields = clazz.getDeclaredFields();
655 
656     AccessibleObject.setAccessible(fields, true);
657 
658     for (Field field : fields) {
659       map.put(field.getName(), field.get(object));
660     }
661 
662     Class<?> superclass = clazz.getSuperclass();
663 
664     if (!Object.class.equals(superclass)) {
665       map.putAll(getVariablesAndValuesFromObject(superclass, object));
666     }
667 
668     return map;
669   }
670 
671   /**
672    * Convenience method to set values to variables in objects that don't have setters
673    *
674    * @param object
675    * @param variable
676    * @param value
677    *
678    * @throws IllegalAccessException
679    */
680   public <T> void setVariableValueToObject(Object object, String variable, T value) throws IllegalAccessException {
681     Field field = ReflectionUtils.getFieldByNameIncludingSuperclasses(variable, object.getClass());
682 
683     field.setAccessible(true);
684 
685     field.set(object, value);
686   }
687 
688   /**
689    * Sometimes the parent element might contain the correct value so generalize that access.
690    * <p>
691    * TODO find out where this is probably done elsewhere
692    *
693    * @param pluginPomDom
694    * @param element
695    *
696    * @return
697    *
698    * @throws Exception
699    */
700   private String resolveFromRootThenParent(Xpp3Dom pluginPomDom, String element) throws Exception {
701     Xpp3Dom elementDom = pluginPomDom.getChild(element);
702 
703     // parent might have the group Id so resolve it
704     if (elementDom == null) {
705       Xpp3Dom pluginParentDom = pluginPomDom.getChild("parent");
706 
707       if (pluginParentDom != null) {
708         elementDom = pluginParentDom.getChild(element);
709 
710         if (elementDom == null) {
711           throw new Exception("unable to determine " + element);
712         }
713 
714         return elementDom.getValue();
715       }
716 
717       throw new Exception("unable to determine " + element);
718     }
719 
720     return elementDom.getValue();
721   }
722 
723   /**
724    * We should make sure this is called in each method that makes use of the container, otherwise we throw ugly NPE's
725    * crops up when the subclassing code defines the setUp method but doesn't call super.setUp()
726    *
727    * @throws Exception
728    */
729   private void validateContainerStatus() throws Exception {
730     if (getContainer() != null) {
731       return;
732     }
733 
734     throw new Exception("container is null, make sure super.setUp() is called");
735   }
736 }