001    /*
002     * SonarQube, open source software quality management tool.
003     * Copyright (C) 2008-2014 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     */
020    package org.sonar.api.batch.maven;
021    
022    import org.apache.commons.lang.StringUtils;
023    import org.apache.commons.lang.builder.ToStringBuilder;
024    import org.apache.maven.model.Plugin;
025    import org.apache.maven.model.ReportPlugin;
026    import org.apache.maven.project.MavenProject;
027    import org.codehaus.plexus.util.xml.Xpp3Dom;
028    
029    import java.util.Collection;
030    import java.util.Iterator;
031    import java.util.List;
032    
033    /**
034     * A class to handle maven plugins
035     *
036     * @since 1.10
037     */
038    public 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    }