IteratorMojo.java

package com.soebes.maven.plugins.iterator;

import java.util.ArrayList;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.BuildPluginManager;
import org.apache.maven.plugin.InvalidPluginDescriptorException;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.PluginConfigurationException;
import org.apache.maven.plugin.PluginDescriptorParsingException;
import org.apache.maven.plugin.PluginManagerException;
import org.apache.maven.plugin.PluginResolutionException;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.reporting.exec.MavenPluginManagerHelper;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomUtils;

/**
 * Executor will execute a given plugin by iterating through the given items.
 *
 * @author Karl-Heinz Marbaise <a href="mailto:khmarbaise@apache.org">khmarbaise@apache.org</a>
 */
@Mojo( name = "iterator", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.TEST, requiresProject = true, threadSafe = true )
public class IteratorMojo
    extends AbstractIteratorMojo
{

    /**
     * By using the pluginExecutors you can define a list of plugins which will be executed during executor.
     * 
     * <pre>
     * {@code
     * <pluginExecutors>
     *   <pluginExecutor>
     *     ..Plugin
     *   </pluginExecutor>
     * </pluginExecutors>
     * }
     * </pre>
     * 
     * A plugin must be defined by using the plugin tag. You can omit the version tag if you have defined the plugin's
     * version in the pluginManagement section.
     * 
     * <pre>
     * {@code
     * <plugin>
     *   <groupId>..</groupId>
     *   <artifactId>..</artifactId>
     *   <version>..</version>
     * </plugin>
     * }
     * </pre>
     * 
     * Furthermore you need to define the goal of the given plugin by using the tag:
     * 
     * <pre>
     * {@code
     * <goal>...</goal>
     * }
     * </pre>
     * 
     * And finally you need to define the configuration for the plugin by using the following:
     * 
     * <pre>
     * {@code
     * <configuration>
     *   Plugin Configuration
     * </configuration>
     * }
     * </pre>
     */
    @Parameter( required = true )
    private List<PluginExecutor> pluginExecutors;

    /**
     * This is the helper class to support Maven 3.1 and before.
     */
    @Component
    protected MavenPluginManagerHelper mavenPluginManagerHelper;

    /**
     * The Maven BuildPluginManager component.
     */
    @Component
    private BuildPluginManager pluginManager;

    @Parameter( defaultValue = "${plugin}", required = true, readonly = true )
    private PluginDescriptor pluginDescriptor;

    /**
     * This will copy the configuration from <b>src</b> to the result whereas the placeholder will be replaced with the
     * current value.
     */
    private PlexusConfiguration copyConfiguration( Xpp3Dom src, String iteratorName, String value )
    {

        XmlPlexusConfiguration dom = new XmlPlexusConfiguration( src.getName() );

        if ( src.getValue() == null )
        {
            dom.setValue( src.getValue() );
        }
        else
        {
            if ( src.getValue().contains( iteratorName ) )
            {
                dom.setValue( src.getValue().replaceAll( iteratorName, value ) );
            }
            else
            {
                dom.setValue( src.getValue() );
            }
        }

        for ( String attributeName : src.getAttributeNames() )
        {
            dom.setAttribute( attributeName, src.getAttribute( attributeName ) );
        }

        for ( Xpp3Dom child : src.getChildren() )
        {
            dom.addChild( copyConfiguration( child, iteratorName, value ) );
        }

        return dom;
    }

    /**
     * This method will give back the plugin information which has been given as parameter in case of an not existing
     * pluginManagement section or if the version of the plugin is already defined. Otherwise the version will be got
     * from the pluginManagement area if the plugin can be found in it.
     * 
     * @param plugin The plugin which should be checked against the pluginManagement area.
     * @return The plugin version. If the plugin version is not defined it means that neither the version has been
     *         defined by the pluginManagement section nor by the user itself.
     */
    private Plugin getPluginVersionFromPluginManagement( Plugin plugin )
    {

        if ( !isPluginManagementDefined() )
        {
            return plugin;
        }

        if ( isPluginVersionDefined( plugin ) )
        {
            return plugin;
        }

        Map<String, Plugin> plugins = getMavenProject().getPluginManagement().getPluginsAsMap();

        Plugin result = plugins.get( plugin.getKey() );
        if ( result == null )
        {
            return plugin;
        }
        else
        {
            return result;
        }
    }

    private boolean isPluginVersionDefined( Plugin plugin )
    {
        return plugin.getVersion() != null;
    }

    private boolean isPluginManagementDefined()
    {
        return getMavenProject().getPluginManagement() != null;
    }

    public void execute()
        throws MojoExecutionException, MojoFailureException
    {

        if ( isSkip() )
        {
            getLog().info( "Skip by user request." );
            return;
        }

        if ( isNoneSet() )
        {
            getLog().warn("Neither items, itemsWithProperties, content nor folder have been set.");
            return;
        }

        if ( isMoreThanOneSet() )
        {
            throw new MojoExecutionException( "You can use only one element. "
                + "Either items, itemsWithProperties, content or folder element but not more than one of them." );
        }

        List<Exception> exceptions = new ArrayList<>();
        for ( ItemWithProperties item : getItemsConverted() )
        {
            for ( PluginExecutor pluginExecutor : pluginExecutors )
            {
                try
                {
                    handlePluginExecution( item, pluginExecutor );
                }
                catch ( MojoExecutionException e )
                {
                    if ( isFailAtEnd() )
                    {
                        exceptions.add( e );
                    }
                    else
                    {
                        throw e;
                    }
                }
                catch ( MojoFailureException e )
                {
                    // An Failure will stop the iteration under any circumstances.
                    throw e;
                }
            }
        }

        if ( !exceptions.isEmpty() )
        {
            for ( Exception exception : exceptions )
            {
                getLog().error( exception );
            }
            throw new MojoExecutionException( "Failures during iteration" );
        }
    }

    /**
     * This will inject the iteration into the current properties and furthermore the properties if they have given and
     * execute the plugin with the appropriate context.
     * 
     * @param item The items which are using for the current iteration.
     * @param pluginExecutor The {@link PluginExecutor}
     * @throws MojoExecutionException in case of an error.
     * @throws MojoFailureException failure during the run of a plugin.
     */
    private void handlePluginExecution( ItemWithProperties item, PluginExecutor pluginExecutor )
        throws MojoExecutionException, MojoFailureException
    {
        Plugin executePlugin = getPluginVersionFromPluginManagement( pluginExecutor.getPlugin() );
        if ( executePlugin.getVersion() == null )
        {
            throw new MojoExecutionException( "Unknown plugin version. You have to define the version either directly or via pluginManagement." );
        }

        Xpp3Dom resultConfiguration = handlePluginConfigurationFromPluginManagement( pluginExecutor, executePlugin );

        PlexusConfiguration plexusConfiguration =
            copyConfiguration( resultConfiguration, getPlaceHolder(), item.getName() );

        createLogOutput( pluginExecutor, executePlugin, item.getName() );

        // Put the value of the current iteration into the current context.
        getMavenProject().getProperties().put( getIteratorName(), item.getName() );

        if ( item.hasProperties() )
        {
            // Add all properties to the context.
            for ( Entry<Object, Object> entry : item.getProperties().entrySet() )
            {
                getMavenProject().getProperties().put( entry.getKey(), entry.getValue() );
            }
        }

        try
        {
            executeMojo( executePlugin, pluginExecutor.getGoal(), toXpp3Dom( plexusConfiguration ) );
        }
        catch ( MojoExecutionException e )
        {
            throw e;
        }
        catch ( MojoFailureException e )
        {
            throw e;
        }
        catch ( PluginResolutionException e )
        {
            getLog().error( "PluginresourceException:", e );
            throw new MojoExecutionException( "PluginRescourceException", e );
        }
        catch ( PluginDescriptorParsingException e )
        {
            getLog().error( "PluginDescriptorParsingException:", e );
            throw new MojoExecutionException( "PluginDescriptorParsingException", e );
        }
        catch ( InvalidPluginDescriptorException e )
        {
            getLog().error( "InvalidPluginDescriptorException:", e );
            throw new MojoExecutionException( "InvalidPluginDescriptorException", e );
        }
        catch ( PluginConfigurationException e )
        {
            getLog().error( "PluginConfigurationException:", e );
            throw new MojoExecutionException( "PluginConfigurationException", e );
        }
        catch ( PluginManagerException e )
        {
            getLog().error( "PluginManagerException:", e );
            throw new MojoExecutionException( "PluginManagerException", e );
        }
        finally
        {
            // Remove the old key from context.
            getMavenProject().getProperties().remove( getIteratorName() );
            if ( item.hasProperties() )
            {
                // Remove all old properties from context.
                for ( Object entry : item.getProperties().keySet() )
                {
                    getMavenProject().getProperties().remove( entry );
                }
            }

        }
    }

    private Xpp3Dom handlePluginConfigurationFromPluginManagement( PluginExecutor pluginExecutor, Plugin executePlugin )
    {
        Xpp3Dom resultConfiguration = toXpp3Dom( pluginExecutor.getConfiguration() );
        getLog().debug( "Configuration: " + resultConfiguration.toString() );
        if ( executePlugin.getConfiguration() != null )
        {
            Xpp3Dom x = (Xpp3Dom) executePlugin.getConfiguration();
            resultConfiguration = Xpp3DomUtils.mergeXpp3Dom( toXpp3Dom( pluginExecutor.getConfiguration() ), x );

            getLog().debug( "ConfigurationExecutePlugin: " + executePlugin.getConfiguration().toString() );

        }
        return resultConfiguration;
    }

    /**
     * Taken from MojoExecutor of Don Brown. Make it working with Maven 3.1.
     * 
     * @param plugin
     * @param goal
     * @param configuration
     * @param env
     * @throws MojoExecutionException
     * @throws PluginResolutionException
     * @throws PluginDescriptorParsingException
     * @throws InvalidPluginDescriptorException
     * @throws PluginManagerException
     * @throws PluginConfigurationException
     * @throws MojoFailureException
     */
    private void executeMojo( Plugin plugin, String goal, Xpp3Dom configuration )
        throws MojoExecutionException, PluginResolutionException, PluginDescriptorParsingException,
        InvalidPluginDescriptorException, MojoFailureException, PluginConfigurationException, PluginManagerException
    {

        if ( configuration == null )
        {
            throw new NullPointerException( "configuration may not be null" );
        }

        PluginDescriptor pluginDescriptor = getPluginDescriptor( plugin );

        MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( goal );
        if ( mojoDescriptor == null )
        {
            throw new MojoExecutionException( "Could not find goal '" + goal + "' in plugin " + plugin.getGroupId()
                + ":" + plugin.getArtifactId() + ":" + plugin.getVersion() );
        }

        MojoExecution exec = mojoExecution( mojoDescriptor, configuration );
        pluginManager.executeMojo( getMavenSession(), exec );
    }

    private PluginDescriptor getPluginDescriptor( Plugin plugin )
        throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException
    {
        return mavenPluginManagerHelper.getPluginDescriptor( plugin, getMavenProject().getRemotePluginRepositories(),
                                                             getMavenSession() );
    }

    private MojoExecution mojoExecution( MojoDescriptor mojoDescriptor, Xpp3Dom configuration )
    {
        Xpp3Dom resultConfiguration =
            Xpp3DomUtils.mergeXpp3Dom( configuration, toXpp3Dom( mojoDescriptor.getMojoConfiguration() ) );
        return new MojoExecution( mojoDescriptor, resultConfiguration );
    }

    /**
     * Taken from MojoExecutor of Don Brown. Converts PlexusConfiguration to a Xpp3Dom.
     * 
     * @param config the PlexusConfiguration. Must not be {@code null}.
     * @return the Xpp3Dom representation of the PlexusConfiguration
     */
    public Xpp3Dom toXpp3Dom( PlexusConfiguration config )
    {
        Xpp3Dom result = new Xpp3Dom( config.getName() );
        result.setValue( config.getValue( null ) );
        for ( String name : config.getAttributeNames() )
        {
            result.setAttribute( name, config.getAttribute( name ) );
        }
        for ( PlexusConfiguration child : config.getChildren() )
        {
            result.addChild( toXpp3Dom( child ) );
        }
        return result;
    }

    /**
     * Will create the output during the executions for the plugins like <code>groupId:artifactId:version:goal</code>.
     * 
     * @param pluginExecutor
     * @param executePlugin
     */
    private void createLogOutput( PluginExecutor pluginExecutor, Plugin executePlugin, String item )
    {
        StringBuilder sb = new StringBuilder( "------ " );
        sb.append( "(" );
        sb.append( item );
        sb.append( ") " );
        sb.append( executePlugin.getKey() );
        sb.append( ":" );
        sb.append( executePlugin.getVersion() );
        sb.append( ":" );
        sb.append( pluginExecutor.getGoal() );
        getLog().info( sb.toString() );
    }

}