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