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