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     */
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 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    }