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.util.xml.Xpp3Dom;
29  
30  /**
31   * Executor of plugins.
32   * @since 0.3
33   */
34  public final class MojoExecutor {
35  
36      /**
37       * Plugin manager.
38       */
39      private final MavenPluginManager manager;
40  
41      /**
42       * Maven session.
43       */
44      private final MavenSession session;
45  
46      /**
47       * Public ctor.
48       * @param mngr The manager
49       * @param sesn Maven session
50       */
51      public MojoExecutor(final MavenPluginManager mngr,
52          final MavenSession sesn) {
53          this.manager = mngr;
54          this.session = sesn;
55      }
56  
57      /**
58       * Find and configure a mojo.
59       * @param coords Maven coordinates,
60       *  e.g. "com.qulice:maven-qulice-plugin:1.0"
61       * @param goal Maven plugin goal to execute
62       * @param config The configuration to set
63       * @throws ValidationException If something is wrong inside
64       */
65      public void execute(final String coords, final String goal,
66          final Properties config) throws ValidationException {
67          final Plugin plugin = new Plugin();
68          final String[] sectors = coords.split(":", -1);
69          plugin.setGroupId(sectors[0]);
70          plugin.setArtifactId(sectors[1]);
71          plugin.setVersion(sectors[2]);
72          final MojoDescriptor descriptor = this.descriptor(plugin, goal);
73          try {
74              new DefaultMavenPluginManagerHelper(this.manager).setupPluginRealm(
75                  descriptor.getPluginDescriptor(),
76                  this.session,
77                  Thread.currentThread().getContextClassLoader(),
78                  List.of(),
79                  List.of()
80              );
81          } catch (final PluginResolutionException ex) {
82              throw new IllegalStateException("Plugin resolution problem", ex);
83          } catch (final PluginContainerException ex) {
84              throw new IllegalStateException("Can't setup realm", ex);
85          }
86          final MojoExecution execution = new MojoExecution(
87              descriptor,
88              Xpp3Dom.mergeXpp3Dom(
89                  this.toXppDom(config, "configuration"),
90                  this.toXppDom(descriptor.getMojoConfiguration())
91              )
92          );
93          final Mojo mojo = this.mojo(execution);
94          try {
95              Logger.info(this, "Calling %s:%s...", coords, goal);
96              mojo.execute();
97          } catch (final MojoExecutionException ex) {
98              throw new IllegalArgumentException(ex);
99          } catch (final MojoFailureException ex) {
100             throw new ValidationException(ex);
101         } finally {
102             this.manager.releaseMojo(mojo, execution);
103         }
104     }
105 
106     /**
107      * Recursively convert Properties to Xpp3Dom.
108      * @param config The config to convert
109      * @param name High-level name of it
110      * @return The Xpp3Dom document
111      * @see #execute(String,String,Properties)
112      */
113     Xpp3Dom toXppDom(final Properties config, final String name) {
114         final Xpp3Dom xpp = new Xpp3Dom(name);
115         for (final Map.Entry<?, ?> entry : config.entrySet()) {
116             xpp.addChild(
117                 this.toNode(entry.getKey().toString(), entry.getValue())
118             );
119         }
120         return xpp;
121     }
122 
123     /**
124      * Recursively convert a Plexus configuration to Xpp3Dom.
125      *
126      * <p>Both {@code getValue()} and {@code getAttribute()} are read
127      * through their two-argument, default-taking forms on purpose. Two
128      * jars on the classpath of a Maven build ship
129      * {@link PlexusConfiguration}: {@code plexus-container-default}, where
130      * the one-argument forms are declared {@code throws
131      * PlexusConfigurationException}, and {@code org.eclipse.sisu.plexus},
132      * where they are not. Whichever of the two {@code javac} sees first
133      * decides whether a {@code catch} of that exception compiles at all,
134      * so calling the one-argument forms makes this class compile or fail
135      * depending on the order of the classpath. The two-argument forms
136      * throw nothing in either jar, and lose nothing here: the names come
137      * from {@code getAttributeNames()}, so every attribute asked for is
138      * present.</p>
139      *
140      * @param config The config to convert
141      * @return The Xpp3Dom document
142      */
143     Xpp3Dom toXppDom(final PlexusConfiguration config) {
144         final Xpp3Dom result = new Xpp3Dom(config.getName());
145         result.setValue(config.getValue(null));
146         for (final String name : config.getAttributeNames()) {
147             result.setAttribute(name, config.getAttribute(name, null));
148         }
149         for (final PlexusConfiguration child : config.getChildren()) {
150             result.addChild(this.toXppDom(child));
151         }
152         return result;
153     }
154 
155     private MojoDescriptor descriptor(final Plugin plugin, final String goal) {
156         try {
157             return new DefaultMavenPluginManagerHelper(this.manager)
158                 .getPluginDescriptor(plugin, this.session)
159                 .getMojo(goal);
160         } catch (final PluginResolutionException ex) {
161             throw new IllegalStateException("Can't resolve plugin", ex);
162         } catch (final PluginDescriptorParsingException ex) {
163             throw new IllegalStateException("Can't parse descriptor", ex);
164         } catch (final InvalidPluginDescriptorException ex) {
165             throw new IllegalStateException("Invalid plugin descriptor", ex);
166         }
167     }
168 
169     private Mojo mojo(final MojoExecution execution) {
170         final Mojo mojo;
171         try {
172             mojo = this.manager.getConfiguredMojo(
173                 Mojo.class, this.session, execution
174             );
175         } catch (final PluginConfigurationException ex) {
176             throw new IllegalStateException("Can't configure MOJO", ex);
177         } catch (final PluginContainerException ex) {
178             throw new IllegalStateException("Plugin container failure", ex);
179         }
180         return mojo;
181     }
182 
183     private Xpp3Dom toNode(final String name, final Object value) {
184         final Xpp3Dom node;
185         if (value instanceof String) {
186             node = new Xpp3Dom(name);
187             node.setValue(String.class.cast(value));
188         } else if (value instanceof String[]) {
189             node = new Xpp3Dom(name);
190             for (final String val : String[].class.cast(value)) {
191                 final Xpp3Dom row = new Xpp3Dom(name);
192                 row.setValue(val);
193                 node.addChild(row);
194             }
195         } else if (value instanceof Collection) {
196             node = new Xpp3Dom(name);
197             for (final Object item : Collection.class.cast(value)) {
198                 this.appendItem(node, name, item);
199             }
200         } else if (value instanceof Properties) {
201             node = this.toXppDom(Properties.class.cast(value), name);
202         } else {
203             throw new IllegalArgumentException(
204                 String.format("Invalid properties value at '%s'", name)
205             );
206         }
207         return node;
208     }
209 
210     private void appendItem(
211         final Xpp3Dom parent, final String name, final Object item
212     ) {
213         if (item instanceof Properties) {
214             final Xpp3Dom converted = this.toXppDom(
215                 Properties.class.cast(item), name
216             );
217             final int count = converted.getChildCount();
218             for (int idx = 0; idx < count; idx += 1) {
219                 parent.addChild(converted.getChild(idx));
220             }
221         } else if (item != null) {
222             parent.addChild(this.toNode(name, item));
223         }
224     }
225 }