View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 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.List;
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   * @since 0.3
34   */
35  public final class MojoExecutor {
36  
37      /**
38       * Plugin manager.
39       */
40      private final MavenPluginManager manager;
41  
42      /**
43       * Maven session.
44       */
45      private final MavenSession session;
46  
47      /**
48       * Public ctor.
49       * @param mngr The manager
50       * @param sesn Maven session
51       */
52      public MojoExecutor(final MavenPluginManager mngr,
53          final MavenSession sesn) {
54          this.manager = mngr;
55          this.session = sesn;
56      }
57  
58      /**
59       * Find and configure a mojo.
60       * @param coords Maven coordinates,
61       *  e.g. "com.qulice:maven-qulice-plugin:1.0"
62       * @param goal Maven plugin goal to execute
63       * @param config The configuration to set
64       * @throws ValidationException If something is wrong inside
65       */
66      public void execute(final String coords, final String goal,
67          final Properties config) throws ValidationException {
68          final Plugin plugin = new Plugin();
69          final String[] sectors = coords.split(":");
70          plugin.setGroupId(sectors[0]);
71          plugin.setArtifactId(sectors[1]);
72          plugin.setVersion(sectors[2]);
73          final MojoDescriptor descriptor = this.descriptor(plugin, goal);
74          try {
75              new DefaultMavenPluginManagerHelper(this.manager).setupPluginRealm(
76                  descriptor.getPluginDescriptor(),
77                  this.session,
78                  Thread.currentThread().getContextClassLoader(),
79                  List.of(),
80                  List.of()
81              );
82          } catch (final PluginResolutionException ex) {
83              throw new IllegalStateException("Plugin resolution problem", ex);
84          } catch (final PluginContainerException ex) {
85              throw new IllegalStateException("Can't setup realm", ex);
86          }
87          final MojoExecution execution = new MojoExecution(
88              descriptor,
89              Xpp3Dom.mergeXpp3Dom(
90                  this.toXppDom(config, "configuration"),
91                  this.toXppDom(descriptor.getMojoConfiguration())
92              )
93          );
94          final Mojo mojo = this.mojo(execution);
95          try {
96              Logger.info(this, "Calling %s:%s...", coords, goal);
97              mojo.execute();
98          } catch (final MojoExecutionException ex) {
99              throw new IllegalArgumentException(ex);
100         } catch (final MojoFailureException ex) {
101             throw new ValidationException(ex);
102         } finally {
103             this.manager.releaseMojo(mojo, execution);
104         }
105     }
106 
107     /**
108      * Recursively convert Properties to Xpp3Dom.
109      * @param config The config to convert
110      * @param name High-level name of it
111      * @return The Xpp3Dom document
112      * @see #execute(String,String,Properties)
113      */
114     Xpp3Dom toXppDom(final Properties config, final String name) {
115         final Xpp3Dom xpp = new Xpp3Dom(name);
116         for (final Map.Entry<?, ?> entry : config.entrySet()) {
117             xpp.addChild(
118                 this.toNode(entry.getKey().toString(), entry.getValue())
119             );
120         }
121         return xpp;
122     }
123 
124     /**
125      * Create descriptor.
126      * @param plugin The plugin
127      * @param goal Maven plugin goal to execute
128      * @return The descriptor
129      */
130     private MojoDescriptor descriptor(final Plugin plugin, final String goal) {
131         try {
132             return new DefaultMavenPluginManagerHelper(this.manager)
133                 .getPluginDescriptor(plugin, this.session)
134                 .getMojo(goal);
135         } catch (final PluginResolutionException ex) {
136             throw new IllegalStateException("Can't resolve plugin", ex);
137         } catch (final PluginDescriptorParsingException ex) {
138             throw new IllegalStateException("Can't parse descriptor", ex);
139         } catch (final InvalidPluginDescriptorException ex) {
140             throw new IllegalStateException("Invalid plugin descriptor", ex);
141         }
142     }
143 
144     /**
145      * Create mojo.
146      * @param execution The execution
147      * @return The mojo
148      */
149     private Mojo mojo(final MojoExecution execution) {
150         final Mojo mojo;
151         try {
152             mojo = this.manager.getConfiguredMojo(
153                 Mojo.class, this.session, execution
154             );
155         } catch (final PluginConfigurationException ex) {
156             throw new IllegalStateException("Can't configure MOJO", ex);
157         } catch (final PluginContainerException ex) {
158             throw new IllegalStateException("Plugin container failure", ex);
159         }
160         return mojo;
161     }
162 
163     /**
164      * Convert a single value into an Xpp3Dom node.
165      * @param name Name of the node
166      * @param value Value to convert
167      * @return The Xpp3Dom node
168      */
169     private Xpp3Dom toNode(final String name, final Object value) {
170         final Xpp3Dom node;
171         if (value instanceof String) {
172             node = new Xpp3Dom(name);
173             node.setValue(String.class.cast(value));
174         } else if (value instanceof String[]) {
175             node = new Xpp3Dom(name);
176             for (final String val : String[].class.cast(value)) {
177                 final Xpp3Dom row = new Xpp3Dom(name);
178                 row.setValue(val);
179                 node.addChild(row);
180             }
181         } else if (value instanceof Collection) {
182             node = new Xpp3Dom(name);
183             for (final Object item : Collection.class.cast(value)) {
184                 this.appendItem(node, name, item);
185             }
186         } else if (value instanceof Properties) {
187             node = this.toXppDom(Properties.class.cast(value), name);
188         } else {
189             throw new IllegalArgumentException(
190                 String.format("Invalid properties value at '%s'", name)
191             );
192         }
193         return node;
194     }
195 
196     /**
197      * Append a collection item to its parent node.
198      * @param parent Parent node receiving the item
199      * @param name Name used for the item when it is not a Properties map
200      * @param item The item to append
201      */
202     private void appendItem(
203         final Xpp3Dom parent, final String name, final Object item
204     ) {
205         if (item instanceof Properties) {
206             final Xpp3Dom converted = this.toXppDom(
207                 Properties.class.cast(item), name
208             );
209             final int count = converted.getChildCount();
210             for (int idx = 0; idx < count; idx += 1) {
211                 parent.addChild(converted.getChild(idx));
212             }
213         } else if (item != null) {
214             parent.addChild(this.toNode(name, item));
215         }
216     }
217 
218     /**
219      * Recursively convert PLEXUS config to Xpp3Dom.
220      * @param config The config to convert
221      * @return The Xpp3Dom document
222      * @see #execute(String,String,Properties)
223      */
224     private Xpp3Dom toXppDom(final PlexusConfiguration config) {
225         final Xpp3Dom result = new Xpp3Dom(config.getName());
226         result.setValue(config.getValue(null));
227         for (final String name : config.getAttributeNames()) {
228             try {
229                 result.setAttribute(name, config.getAttribute(name));
230             } catch (final PlexusConfigurationException ex) {
231                 throw new IllegalArgumentException(ex);
232             }
233         }
234         for (final PlexusConfiguration child : config.getChildren()) {
235             result.addChild(this.toXppDom(child));
236         }
237         return result;
238     }
239 }