001/*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2013 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * SonarQube is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019 */
020package org.sonar.api.batch.maven;
021
022import org.apache.commons.lang.StringUtils;
023import org.apache.commons.lang.builder.ToStringBuilder;
024import org.apache.maven.model.Plugin;
025import org.apache.maven.model.ReportPlugin;
026import org.apache.maven.project.MavenProject;
027import org.codehaus.plexus.util.xml.Xpp3Dom;
028
029import java.util.Collection;
030import java.util.Iterator;
031import java.util.List;
032
033/**
034 * A class to handle maven plugins
035 *
036 * @since 1.10
037 */
038public class MavenPlugin {
039
040  private static final String CONFIGURATION_ELEMENT = "configuration";
041  private Plugin plugin;
042  private Xpp3Dom configuration;
043
044  /**
045   * Creates a MavenPlugin based on a Plugin
046   *
047   * @param plugin the plugin
048   */
049  public MavenPlugin(Plugin plugin) {
050    this.plugin = plugin;
051    this.configuration = (Xpp3Dom) plugin.getConfiguration();
052    if (this.configuration == null) {
053      configuration = new Xpp3Dom(CONFIGURATION_ELEMENT);
054      plugin.setConfiguration(this.configuration);
055    }
056  }
057
058  /**
059   * Creates a Maven plugin based on artifact + group + version
060   *
061   * @param groupId the group id
062   * @param artifactId the artifact id
063   * @param version the version
064   */
065  public MavenPlugin(String groupId, String artifactId, String version) {
066    this.plugin = new Plugin();
067    plugin.setGroupId(groupId);
068    plugin.setArtifactId(artifactId);
069    plugin.setVersion(version);
070    configuration = new Xpp3Dom(CONFIGURATION_ELEMENT);
071    plugin.setConfiguration(this.configuration);
072  }
073
074  /**
075   * @since 3.5 - see SONAR-4070
076   * @return the XML node <configuration> of pom
077   */
078  public Xpp3Dom getConfigurationXmlNode() {
079    return configuration;
080  }
081
082  /**
083   * Sets the maven plugin version
084   *
085   * @param version the version
086   * @return this
087   */
088  public MavenPlugin setVersion(String version) {
089    this.plugin.setVersion(version);
090    return this;
091  }
092
093  /**
094   * @return the underlying plugin
095   */
096  public Plugin getPlugin() {
097    return plugin;
098  }
099
100  /**
101   * Gets a parameter of the plugin based on its key
102   *
103   * @param key the param key
104   * @return the parameter if exist, null otherwise
105   */
106  public String getParameter(String key) {
107    Xpp3Dom node = findNodeWith(key);
108    return node == null ? null : node.getValue();
109  }
110
111  /**
112   * Gets a list of parameters of the plugin from a param key
113   *
114   * @param key param key with option-index snippet: e.g. item[0], item[1]. If no index snippet is passed, then
115   *            0 is default (index <=> index[0])
116   * @return an array of parameters if any, an empty array otherwise
117   */
118  public String[] getParameters(String key) {
119    String[] keyParts = StringUtils.split(key, "/");
120    Xpp3Dom node = configuration;
121    for (int i = 0; i < keyParts.length - 1; i++) {
122      node = getOrCreateChild(node, keyParts[i]);
123    }
124    Xpp3Dom[] children = node.getChildren(keyParts[keyParts.length - 1]);
125    String[] result = new String[children.length];
126    for (int i = 0; i < children.length; i++) {
127      result[i] = children[i].getValue();
128    }
129    return result;
130  }
131
132  /**
133   * Sets a parameter for the maven plugin. This will overrides an existing parameter.
134   *
135   * @param key the param key
136   * @param value the param value
137   * @return this
138   */
139  public MavenPlugin setParameter(String key, String value) {
140    checkKeyArgument(key);
141    String[] keyParts = StringUtils.split(key, "/");
142    Xpp3Dom node = configuration;
143    for (String keyPart : keyParts) {
144      node = getOrCreateChild(node, keyPart);
145    }
146    node.setValue(value);
147    return this;
148  }
149
150  /**
151   * Sets a parameter to the maven plugin. Overrides existing parameter only id specified.
152   *
153   * @param key the param key
154   * @param value the param value
155   * @param override whether to override existing parameter
156   */
157  public void setParameter(String key, String value, boolean override) {
158    if (getParameter(key) == null || override) {
159      setParameter(key, value);
160    }
161  }
162
163  /**
164   * Removes all parameters from the maven plugin
165   */
166  public void removeParameters() {
167    configuration = new Xpp3Dom(CONFIGURATION_ELEMENT);
168    plugin.setConfiguration(this.configuration);
169  }
170
171  /**
172   * Adds a parameter to the maven plugin
173   *
174   * @param key the param key with option-index snippet: e.g. item[0], item[1]. If no index snippet is passed, then
175   *            0 is default (index <=> index[0])
176   * @param value the param value
177   * @return this
178   */
179  public MavenPlugin addParameter(String key, String value) {
180    String[] keyParts = StringUtils.split(key, "/");
181    Xpp3Dom node = configuration;
182    for (int i = 0; i < keyParts.length - 1; i++) {
183      node = getOrCreateChild(node, keyParts[i]);
184    }
185    Xpp3Dom leaf = new Xpp3Dom(keyParts[keyParts.length - 1]);
186    leaf.setValue(value);
187    node.addChild(leaf);
188    return this;
189  }
190
191  private static Xpp3Dom getOrCreateChild(Xpp3Dom node, String key) {
192    int childIndex = getIndex(key);
193
194    if (node.getChildren(removeIndexSnippet(key)).length <= childIndex) {
195      Xpp3Dom child = new Xpp3Dom(removeIndexSnippet(key));
196      node.addChild(child);
197      return child;
198    }
199    return node.getChildren(removeIndexSnippet(key))[childIndex];
200
201  }
202
203  private static int getIndex(String key) {
204    // parsing index-syntax (e.g. item[1])
205    if (key.matches(".*?\\[\\d+\\]")) {
206      return Integer.parseInt(StringUtils.substringBetween(key, "[", "]"));
207    }
208    // for down-compatibility of api we fallback to default 0
209    return 0;
210  }
211
212  private static String removeIndexSnippet(String key) {
213    return StringUtils.substringBefore(key, "[");
214  }
215
216  /**
217   * Remove a parameter from the maven plugin based on its key
218   *
219   * @param key param key with option-index snippet: e.g. item[0], item[1]. If no index snippet is passed, then
220   *            0 is default (index <=> index[0])
221   */
222  public void removeParameter(String key) {
223    Xpp3Dom node = findNodeWith(key);
224    if (node != null) {
225      remove(node);
226    }
227  }
228
229  private Xpp3Dom findNodeWith(String key) {
230    checkKeyArgument(key);
231    String[] keyParts = key.split("/");
232    Xpp3Dom node = configuration;
233    for (String keyPart : keyParts) {
234
235      if (node.getChildren(removeIndexSnippet(keyPart)).length <= getIndex(keyPart)) {
236        return null;
237      }
238
239      node = node.getChildren(removeIndexSnippet(keyPart))[getIndex(keyPart)];
240      if (node == null) {
241        return null;
242      }
243    }
244    return node;
245  }
246
247  private static void remove(Xpp3Dom node) {
248    Xpp3Dom parent = node.getParent();
249    for (int i = 0; i < parent.getChildCount(); i++) {
250      Xpp3Dom child = parent.getChild(i);
251      if (child.equals(node)) {
252        parent.removeChild(i);
253        break;
254      }
255    }
256  }
257
258  /**
259   * @return whether the maven plugin has got configuration
260   */
261  public boolean hasConfiguration() {
262    return configuration.getChildCount() > 0;
263  }
264
265  private static void checkKeyArgument(String key) {
266    if (key == null) {
267      throw new IllegalArgumentException("Parameter 'key' should not be null.");
268    }
269  }
270
271  /**
272   * Registers a plugin in a project pom
273   * <p/>
274   * <p>Adds the plugin if it does not exist or amend its version if it does exist and specified</p>
275   *
276   * @param pom the project pom
277   * @param groupId the plugin group id
278   * @param artifactId the plugin artifact id
279   * @param version the plugin version
280   * @param overrideVersion whether to override the version if the plugin is already registered
281   * @return the registered plugin
282   */
283  public static MavenPlugin registerPlugin(MavenProject pom, String groupId, String artifactId, String version, boolean overrideVersion) {
284    MavenPlugin plugin = getPlugin(pom, groupId, artifactId);
285    if (plugin == null) {
286      plugin = new MavenPlugin(groupId, artifactId, version);
287
288    } else if (overrideVersion) {
289      plugin.setVersion(version);
290    }
291
292    // remove from pom
293    unregisterPlugin(pom, groupId, artifactId);
294
295    // register
296    pom.getBuild().addPlugin(plugin.getPlugin());
297
298    return plugin;
299  }
300
301  /**
302   * Returns a plugin from a pom based on its group id and artifact id
303   * <p/>
304   * <p>It searches in the build section, then the reporting section and finally the pluginManagement section</p>
305   *
306   * @param pom the project pom
307   * @param groupId the plugin group id
308   * @param artifactId the plugin artifact id
309   * @return the plugin if it exists, null otherwise
310   */
311  public static MavenPlugin getPlugin(MavenProject pom, String groupId, String artifactId) {
312    if (pom == null) {
313      return null;
314    }
315    // look for plugin in <build> section
316    Plugin plugin = null;
317    if (pom.getBuildPlugins() != null) {
318      plugin = getPlugin(pom.getBuildPlugins(), groupId, artifactId);
319    }
320
321    // look for plugin in <report> section
322    if (plugin == null && pom.getReportPlugins() != null) {
323      plugin = getReportPlugin(pom.getReportPlugins(), groupId, artifactId);
324    }
325
326    // look for plugin in <pluginManagement> section
327    if (pom.getPluginManagement() != null) {
328      Plugin pluginManagement = getPlugin(pom.getPluginManagement().getPlugins(), groupId, artifactId);
329      if (plugin == null) {
330        plugin = pluginManagement;
331
332      } else if (pluginManagement != null) {
333        if (pluginManagement.getConfiguration() != null) {
334          if (plugin.getConfiguration() == null) {
335            plugin.setConfiguration(pluginManagement.getConfiguration());
336          } else {
337            Xpp3Dom.mergeXpp3Dom((Xpp3Dom) plugin.getConfiguration(), (Xpp3Dom) pluginManagement.getConfiguration());
338          }
339        }
340        if (plugin.getDependencies() == null && pluginManagement.getDependencies() != null) {
341          plugin.setDependencies(pluginManagement.getDependencies());
342        }
343        if (plugin.getVersion() == null) {
344          plugin.setVersion(pluginManagement.getVersion());
345        }
346      }
347    }
348
349    if (plugin != null) {
350      return new MavenPlugin(plugin);
351    }
352    return null;
353  }
354
355  private static Plugin getPlugin(Collection<Plugin> plugins, String groupId, String artifactId) {
356    if (plugins == null) {
357      return null;
358    }
359
360    for (Plugin plugin : plugins) {
361      if (MavenUtils.equals(plugin, groupId, artifactId)) {
362        return plugin;
363      }
364    }
365    return null;
366  }
367
368  private static Plugin getReportPlugin(Collection<ReportPlugin> plugins, String groupId, String artifactId) {
369    if (plugins == null) {
370      return null;
371    }
372
373    for (ReportPlugin plugin : plugins) {
374      if (MavenUtils.equals(plugin, groupId, artifactId)) {
375        return cloneReportPluginToPlugin(plugin);
376      }
377    }
378    return null;
379  }
380
381  private static Plugin cloneReportPluginToPlugin(ReportPlugin reportPlugin) {
382    Plugin plugin = new Plugin();
383    plugin.setGroupId(reportPlugin.getGroupId());
384    plugin.setArtifactId(reportPlugin.getArtifactId());
385    plugin.setVersion(reportPlugin.getVersion());
386    plugin.setConfiguration(reportPlugin.getConfiguration());
387    return plugin;
388  }
389
390  private static void unregisterPlugin(MavenProject pom, String groupId, String artifactId) {
391    if (pom.getPluginManagement() != null && pom.getPluginManagement().getPlugins() != null) {
392      unregisterPlugin(pom.getPluginManagement().getPlugins(), groupId, artifactId);
393    }
394    if (pom.getBuildPlugins() != null && pom.getBuildPlugins() != null) {
395      unregisterPlugin(pom.getBuildPlugins(), groupId, artifactId);
396    }
397    if (pom.getReportPlugins() != null) {
398      unregisterReportPlugin(pom.getReportPlugins(), groupId, artifactId);
399    }
400  }
401
402  private static void unregisterPlugin(List<Plugin> plugins, String groupId, String artifactId) {
403    for (Iterator<Plugin> iterator = plugins.iterator(); iterator.hasNext();) {
404      Plugin p = iterator.next();
405      if (MavenUtils.equals(p, groupId, artifactId)) {
406        iterator.remove();
407      }
408    }
409  }
410
411  private static void unregisterReportPlugin(List<ReportPlugin> plugins, String groupId, String artifactId) {
412    for (Iterator<ReportPlugin> iterator = plugins.iterator(); iterator.hasNext();) {
413      ReportPlugin p = iterator.next();
414      if (MavenUtils.equals(p, groupId, artifactId)) {
415        iterator.remove();
416      }
417    }
418  }
419
420  @Override
421  public String toString() {
422    return new ToStringBuilder(this)
423      .append("groupId", plugin.getGroupId())
424      .append("artifactId", plugin.getArtifactId())
425      .append("version", plugin.getVersion())
426      .toString();
427  }
428}