View Javadoc
1   /*
2    * Copyright (c) 2011-2024 Qulice.com
3    *
4    * All rights reserved.
5    *
6    * Redistribution and use in source and binary forms, with or without
7    * modification, are permitted provided that the following conditions
8    * are met: 1) Redistributions of source code must retain the above
9    * copyright notice, this list of conditions and the following
10   * disclaimer. 2) Redistributions in binary form must reproduce the above
11   * copyright notice, this list of conditions and the following
12   * disclaimer in the documentation and/or other materials provided
13   * with the distribution. 3) Neither the name of the Qulice.com nor
14   * the names of its contributors may be used to endorse or promote
15   * products derived from this software without specific prior written
16   * permission.
17   *
18   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
20   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
21   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22   * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29   * OF THE POSSIBILITY OF SUCH DAMAGE.
30   */
31  package com.qulice.maven;
32  
33  import com.jcabi.log.Logger;
34  import com.qulice.spi.ValidationException;
35  import java.util.Collection;
36  import java.util.LinkedList;
37  import java.util.Map;
38  import java.util.Properties;
39  import org.apache.maven.execution.MavenSession;
40  import org.apache.maven.model.Plugin;
41  import org.apache.maven.plugin.InvalidPluginDescriptorException;
42  import org.apache.maven.plugin.MavenPluginManager;
43  import org.apache.maven.plugin.Mojo;
44  import org.apache.maven.plugin.MojoExecution;
45  import org.apache.maven.plugin.MojoExecutionException;
46  import org.apache.maven.plugin.MojoFailureException;
47  import org.apache.maven.plugin.PluginConfigurationException;
48  import org.apache.maven.plugin.PluginContainerException;
49  import org.apache.maven.plugin.PluginDescriptorParsingException;
50  import org.apache.maven.plugin.PluginResolutionException;
51  import org.apache.maven.plugin.descriptor.MojoDescriptor;
52  import org.apache.maven.reporting.exec.DefaultMavenPluginManagerHelper;
53  import org.codehaus.plexus.configuration.PlexusConfiguration;
54  import org.codehaus.plexus.configuration.PlexusConfigurationException;
55  import org.codehaus.plexus.util.xml.Xpp3Dom;
56  
57  /**
58   * Executor of plugins.
59   *
60   * @since 0.3
61   */
62  public final class MojoExecutor {
63  
64      /**
65       * Plugin manager.
66       */
67      private final MavenPluginManager manager;
68  
69      /**
70       * Maven session.
71       */
72      private final MavenSession session;
73  
74      /**
75       * Helper for plugin manager.
76       */
77      private final DefaultMavenPluginManagerHelper helper;
78  
79      /**
80       * Public ctor.
81       * @param mngr The manager
82       * @param sesn Maven session
83       */
84      @SuppressWarnings({"PMD.NonStaticInitializer", "PMD.DoubleBraceInitialization"})
85      public MojoExecutor(final MavenPluginManager mngr,
86          final MavenSession sesn) {
87          this.manager = mngr;
88          this.session = sesn;
89          this.helper = new DefaultMavenPluginManagerHelper() {
90              {
91                  this.mavenPluginManager = MojoExecutor.this.manager;
92              }
93          };
94      }
95  
96      /**
97       * Find and configure a mojor.
98       * @param coords Maven coordinates,
99       *  e.g. "com.qulice:maven-qulice-plugin:1.0"
100      * @param goal Maven plugin goal to execute
101      * @param config The configuration to set
102      * @throws ValidationException If something is wrong inside
103      */
104     public void execute(final String coords, final String goal,
105         final Properties config) throws ValidationException {
106         final Plugin plugin = new Plugin();
107         final String[] sectors = coords.split(":");
108         plugin.setGroupId(sectors[0]);
109         plugin.setArtifactId(sectors[1]);
110         plugin.setVersion(sectors[2]);
111         final MojoDescriptor descriptor = this.descriptor(plugin, goal);
112         try {
113             this.helper.setupPluginRealm(
114                 descriptor.getPluginDescriptor(),
115                 this.session,
116                 Thread.currentThread().getContextClassLoader(),
117                 new LinkedList<String>(),
118                 new LinkedList<String>()
119             );
120         } catch (final PluginResolutionException ex) {
121             throw new IllegalStateException("Plugin resolution problem", ex);
122         } catch (final PluginContainerException ex) {
123             throw new IllegalStateException("Can't setup realm", ex);
124         }
125         final Xpp3Dom xpp = Xpp3Dom.mergeXpp3Dom(
126             this.toXppDom(config, "configuration"),
127             this.toXppDom(descriptor.getMojoConfiguration())
128         );
129         final MojoExecution execution = new MojoExecution(descriptor, xpp);
130         final Mojo mojo = this.mojo(execution);
131         try {
132             Logger.info(this, "Calling %s:%s...", coords, goal);
133             mojo.execute();
134         } catch (final MojoExecutionException ex) {
135             throw new IllegalArgumentException(ex);
136         } catch (final MojoFailureException ex) {
137             throw new ValidationException(ex);
138         } finally {
139             this.manager.releaseMojo(mojo, execution);
140         }
141     }
142 
143     /**
144      * Create descriptor.
145      * @param plugin The plugin
146      * @param goal Maven plugin goal to execute
147      * @return The descriptor
148      */
149     private MojoDescriptor descriptor(final Plugin plugin, final String goal) {
150         try {
151             return this.helper.getPluginDescriptor(
152                 plugin,
153                 this.session
154             ).getMojo(goal);
155         } catch (final PluginResolutionException ex) {
156             throw new IllegalStateException("Can't resolve plugin", ex);
157         } catch (final PluginDescriptorParsingException ex) {
158             throw new IllegalStateException("Can't parse descriptor", ex);
159         } catch (final InvalidPluginDescriptorException ex) {
160             throw new IllegalStateException("Invalid plugin descriptor", ex);
161         }
162     }
163 
164     /**
165      * Create mojo.
166      * @param execution The execution
167      * @return The mojo
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     /**
184      * Recuresively convert Properties to Xpp3Dom.
185      * @param config The config to convert
186      * @param name High-level name of it
187      * @return The Xpp3Dom document
188      * @see #execute(String,String,Properties)
189      * @checkstyle ExecutableStatementCountCheck (100 lines)
190      */
191     @SuppressWarnings({"PMD.AvoidInstantiatingObjectsInLoops", "PMD.CognitiveComplexity"})
192     private Xpp3Dom toXppDom(final Properties config, final String name) {
193         final Xpp3Dom xpp = new Xpp3Dom(name);
194         for (final Map.Entry<?, ?> entry : config.entrySet()) {
195             if (entry.getValue() instanceof String) {
196                 final Xpp3Dom child = new Xpp3Dom(entry.getKey().toString());
197                 child.setValue(config.getProperty(entry.getKey().toString()));
198                 xpp.addChild(child);
199             } else if (entry.getValue() instanceof String[]) {
200                 final Xpp3Dom child = new Xpp3Dom(entry.getKey().toString());
201                 for (final String val : String[].class.cast(entry.getValue())) {
202                     final Xpp3Dom row = new Xpp3Dom(entry.getKey().toString());
203                     row.setValue(val);
204                     child.addChild(row);
205                 }
206                 xpp.addChild(child);
207             } else if (entry.getValue() instanceof Collection) {
208                 final Xpp3Dom child = new Xpp3Dom(entry.getKey().toString());
209                 for (final Object val : Collection.class.cast(entry.getValue())) {
210                     if (val instanceof Properties) {
211                         child.addChild(
212                             this.toXppDom(
213                                 Properties.class.cast(val),
214                                 entry.getKey().toString()
215                             ).getChild(0)
216                         );
217                     } else if (val != null) {
218                         final Xpp3Dom row = new Xpp3Dom(entry.getKey().toString());
219                         row.setValue(val.toString());
220                         child.addChild(row);
221                     }
222                 }
223                 xpp.addChild(child);
224             } else if (entry.getValue() instanceof Properties) {
225                 xpp.addChild(
226                     this.toXppDom(
227                         Properties.class.cast(entry.getValue()),
228                         entry.getKey().toString()
229                     )
230                 );
231             } else {
232                 throw new IllegalArgumentException(
233                     String.format(
234                         "Invalid properties value at '%s'",
235                         entry.getKey().toString()
236                     )
237                 );
238             }
239         }
240         return xpp;
241     }
242 
243     /**
244      * Recursively convert PLEXUS config to Xpp3Dom.
245      * @param config The config to convert
246      * @return The Xpp3Dom document
247      * @see #execute(String,String,Properties)
248      */
249     private Xpp3Dom toXppDom(final PlexusConfiguration config) {
250         final Xpp3Dom result = new Xpp3Dom(config.getName());
251         result.setValue(config.getValue(null));
252         for (final String name : config.getAttributeNames()) {
253             try {
254                 result.setAttribute(name, config.getAttribute(name));
255             } catch (final PlexusConfigurationException ex) {
256                 throw new IllegalArgumentException(ex);
257             }
258         }
259         for (final PlexusConfiguration child : config.getChildren()) {
260             result.addChild(this.toXppDom(child));
261         }
262         return result;
263     }
264 
265 }