View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.maven;
6   
7   import com.jcabi.log.Logger;
8   import com.qulice.spi.ValidationException;
9   import java.util.Collection;
10  import java.util.LinkedList;
11  import java.util.Map;
12  import java.util.Properties;
13  import org.apache.maven.execution.MavenSession;
14  import org.apache.maven.model.Plugin;
15  import org.apache.maven.plugin.InvalidPluginDescriptorException;
16  import org.apache.maven.plugin.MavenPluginManager;
17  import org.apache.maven.plugin.Mojo;
18  import org.apache.maven.plugin.MojoExecution;
19  import org.apache.maven.plugin.MojoExecutionException;
20  import org.apache.maven.plugin.MojoFailureException;
21  import org.apache.maven.plugin.PluginConfigurationException;
22  import org.apache.maven.plugin.PluginContainerException;
23  import org.apache.maven.plugin.PluginDescriptorParsingException;
24  import org.apache.maven.plugin.PluginResolutionException;
25  import org.apache.maven.plugin.descriptor.MojoDescriptor;
26  import org.apache.maven.reporting.exec.DefaultMavenPluginManagerHelper;
27  import org.codehaus.plexus.configuration.PlexusConfiguration;
28  import org.codehaus.plexus.configuration.PlexusConfigurationException;
29  import org.codehaus.plexus.util.xml.Xpp3Dom;
30  
31  /**
32   * Executor of plugins.
33   *
34   * @since 0.3
35   */
36  public final class MojoExecutor {
37  
38      /**
39       * Plugin manager.
40       */
41      private final MavenPluginManager manager;
42  
43      /**
44       * Maven session.
45       */
46      private final MavenSession session;
47  
48      /**
49       * Helper for plugin manager.
50       */
51      private final DefaultMavenPluginManagerHelper helper;
52  
53      /**
54       * Public ctor.
55       * @param mngr The manager
56       * @param sesn Maven session
57       */
58      @SuppressWarnings({"PMD.NonStaticInitializer", "PMD.DoubleBraceInitialization"})
59      public MojoExecutor(final MavenPluginManager mngr,
60          final MavenSession sesn) {
61          this.manager = mngr;
62          this.session = sesn;
63          this.helper = new DefaultMavenPluginManagerHelper() {
64              {
65                  this.mavenPluginManager = MojoExecutor.this.manager;
66              }
67          };
68      }
69  
70      /**
71       * Find and configure a mojor.
72       * @param coords Maven coordinates,
73       *  e.g. "com.qulice:maven-qulice-plugin:1.0"
74       * @param goal Maven plugin goal to execute
75       * @param config The configuration to set
76       * @throws ValidationException If something is wrong inside
77       */
78      public void execute(final String coords, final String goal,
79          final Properties config) throws ValidationException {
80          final Plugin plugin = new Plugin();
81          final String[] sectors = coords.split(":");
82          plugin.setGroupId(sectors[0]);
83          plugin.setArtifactId(sectors[1]);
84          plugin.setVersion(sectors[2]);
85          final MojoDescriptor descriptor = this.descriptor(plugin, goal);
86          try {
87              this.helper.setupPluginRealm(
88                  descriptor.getPluginDescriptor(),
89                  this.session,
90                  Thread.currentThread().getContextClassLoader(),
91                  new LinkedList<String>(),
92                  new LinkedList<String>()
93              );
94          } catch (final PluginResolutionException ex) {
95              throw new IllegalStateException("Plugin resolution problem", ex);
96          } catch (final PluginContainerException ex) {
97              throw new IllegalStateException("Can't setup realm", ex);
98          }
99          final Xpp3Dom xpp = Xpp3Dom.mergeXpp3Dom(
100             this.toXppDom(config, "configuration"),
101             this.toXppDom(descriptor.getMojoConfiguration())
102         );
103         final MojoExecution execution = new MojoExecution(descriptor, xpp);
104         final Mojo mojo = this.mojo(execution);
105         try {
106             Logger.info(this, "Calling %s:%s...", coords, goal);
107             mojo.execute();
108         } catch (final MojoExecutionException ex) {
109             throw new IllegalArgumentException(ex);
110         } catch (final MojoFailureException ex) {
111             throw new ValidationException(ex);
112         } finally {
113             this.manager.releaseMojo(mojo, execution);
114         }
115     }
116 
117     /**
118      * Create descriptor.
119      * @param plugin The plugin
120      * @param goal Maven plugin goal to execute
121      * @return The descriptor
122      */
123     private MojoDescriptor descriptor(final Plugin plugin, final String goal) {
124         try {
125             return this.helper.getPluginDescriptor(
126                 plugin,
127                 this.session
128             ).getMojo(goal);
129         } catch (final PluginResolutionException ex) {
130             throw new IllegalStateException("Can't resolve plugin", ex);
131         } catch (final PluginDescriptorParsingException ex) {
132             throw new IllegalStateException("Can't parse descriptor", ex);
133         } catch (final InvalidPluginDescriptorException ex) {
134             throw new IllegalStateException("Invalid plugin descriptor", ex);
135         }
136     }
137 
138     /**
139      * Create mojo.
140      * @param execution The execution
141      * @return The mojo
142      */
143     private Mojo mojo(final MojoExecution execution) {
144         final Mojo mojo;
145         try {
146             mojo = this.manager.getConfiguredMojo(
147                 Mojo.class, this.session, execution
148             );
149         } catch (final PluginConfigurationException ex) {
150             throw new IllegalStateException("Can't configure MOJO", ex);
151         } catch (final PluginContainerException ex) {
152             throw new IllegalStateException("Plugin container failure", ex);
153         }
154         return mojo;
155     }
156 
157     /**
158      * Recuresively convert Properties to Xpp3Dom.
159      * @param config The config to convert
160      * @param name High-level name of it
161      * @return The Xpp3Dom document
162      * @see #execute(String,String,Properties)
163      * @checkstyle ExecutableStatementCountCheck (100 lines)
164      */
165     @SuppressWarnings({"PMD.AvoidInstantiatingObjectsInLoops", "PMD.CognitiveComplexity"})
166     private Xpp3Dom toXppDom(final Properties config, final String name) {
167         final Xpp3Dom xpp = new Xpp3Dom(name);
168         for (final Map.Entry<?, ?> entry : config.entrySet()) {
169             if (entry.getValue() instanceof String) {
170                 final Xpp3Dom child = new Xpp3Dom(entry.getKey().toString());
171                 child.setValue(config.getProperty(entry.getKey().toString()));
172                 xpp.addChild(child);
173             } else if (entry.getValue() instanceof String[]) {
174                 final Xpp3Dom child = new Xpp3Dom(entry.getKey().toString());
175                 for (final String val : String[].class.cast(entry.getValue())) {
176                     final Xpp3Dom row = new Xpp3Dom(entry.getKey().toString());
177                     row.setValue(val);
178                     child.addChild(row);
179                 }
180                 xpp.addChild(child);
181             } else if (entry.getValue() instanceof Collection) {
182                 final Xpp3Dom child = new Xpp3Dom(entry.getKey().toString());
183                 for (final Object val : Collection.class.cast(entry.getValue())) {
184                     if (val instanceof Properties) {
185                         child.addChild(
186                             this.toXppDom(
187                                 Properties.class.cast(val),
188                                 entry.getKey().toString()
189                             ).getChild(0)
190                         );
191                     } else if (val != null) {
192                         final Xpp3Dom row = new Xpp3Dom(entry.getKey().toString());
193                         row.setValue(val.toString());
194                         child.addChild(row);
195                     }
196                 }
197                 xpp.addChild(child);
198             } else if (entry.getValue() instanceof Properties) {
199                 xpp.addChild(
200                     this.toXppDom(
201                         Properties.class.cast(entry.getValue()),
202                         entry.getKey().toString()
203                     )
204                 );
205             } else {
206                 throw new IllegalArgumentException(
207                     String.format(
208                         "Invalid properties value at '%s'",
209                         entry.getKey().toString()
210                     )
211                 );
212             }
213         }
214         return xpp;
215     }
216 
217     /**
218      * Recursively convert PLEXUS config to Xpp3Dom.
219      * @param config The config to convert
220      * @return The Xpp3Dom document
221      * @see #execute(String,String,Properties)
222      */
223     private Xpp3Dom toXppDom(final PlexusConfiguration config) {
224         final Xpp3Dom result = new Xpp3Dom(config.getName());
225         result.setValue(config.getValue(null));
226         for (final String name : config.getAttributeNames()) {
227             try {
228                 result.setAttribute(name, config.getAttribute(name));
229             } catch (final PlexusConfigurationException ex) {
230                 throw new IllegalArgumentException(ex);
231             }
232         }
233         for (final PlexusConfiguration child : config.getChildren()) {
234             result.addChild(this.toXppDom(child));
235         }
236         return result;
237     }
238 
239 }