Package org.apache.maven.plugin

Source Code of org.apache.maven.plugin.PluginManager

package org.apache.maven.plugin;

/* ====================================================================
*   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.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.apache.commons.jelly.Script;
import org.apache.commons.jelly.expression.Expression;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.maven.AbstractMavenComponent;
import org.apache.maven.AntProjectBuilder;
import org.apache.maven.MavenConstants;
import org.apache.maven.MavenException;
import org.apache.maven.MavenSession;
import org.apache.maven.MavenUtils;
import org.apache.maven.UnknownGoalException;
import org.apache.maven.jelly.JellyUtils;
import org.apache.maven.jelly.MavenJellyContext;
import org.apache.maven.project.Dependency;
import org.apache.maven.project.Project;
import org.apache.maven.repository.Artifact;
import org.apache.maven.util.Expand;
import org.apache.maven.werkz.Goal;
import org.apache.maven.werkz.NoSuchGoalException;
import org.apache.maven.werkz.Session;
import org.apache.maven.werkz.WerkzProject;
import org.apache.maven.werkz.jelly.JellySession;

import com.werken.forehead.Forehead;
import com.werken.forehead.ForeheadClassLoader;

/*

NOTES:

We initialize the plugin with an empty context. When we are finished initializing the
plugins we will have a context with a werkz project object that has been created
that will contain all the goals present within the plugins.

The result of this initialization is the creation of all the cache files which
give us an outline of what is required to attain a particular goal.

When a particular project needs to attain one of these goals we need to
load the plugin and in doing so we run the plugin.jelly script for the required
plugins against the projects context.

Possibly eventually we could make this more efficient storing the plugin.jelly
in an accessible templated form which when required for a particular project
can be executed against the project's context.

IMPLEMENTATION NOTES

- We need to know the goals to attain
- We need to keep track of plugins that have been loaded for a particular project.

*/

/**
* Plugin manager for MavenSession.
* <p/>
* The <code>PluginManager</code> deals with all aspects of a plugins lifecycle.
* <p/>
* This is <b>not</b> thread safe.
*
* @author <a href="mailto:jason@zenplex.com">Jason van Zyl</a>
* @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
* @author <a href="mailto:brett@apache.org">Brett Porter</a>
* @version $Id: PluginManager.java 517014 2007-03-11 21:15:50Z ltheussl $
*/
public class PluginManager
    extends AbstractMavenComponent
{
    /**
     * Logger
     */
    private static final Log LOGGER = LogFactory.getLog( PluginManager.class );

    /**
     * The variable key that holds an implementation of the
     * <code>com.werken.werkz.Session</code> in the parent scope context.
     */
    public static final String GLOBAL_SESSION_KEY = "maven.session.global";

    public static final String PLUGIN_MANAGER = "maven.plugin.manager";

    public static final String PLUGIN_HOUSING = "maven.plugin.script.housing";

    public static final String GOAL_MAPPER = "maven.plugin.mapper";

    /** */
    public static final String BASE_CONTEXT = "maven.goalAttainmentContext";

    /**
     * The directory where plugin jars reside under Maven's home.
     */
    private File pluginsDir;

    /**
     * The directory where the plugin jars are unpacked to.
     */
    private File unpackedPluginsDir;

    /**
     * The directory where the user's plugin jars are installed.
     */
    private File userPluginsDir;

    /**
     * This contains a map of plugins, keyed by id.
     */
    private final Map pluginHousings = new HashMap();

    /**
     * This contains a map of plugins, keyed by artifact id.
     */
    private final Map artifactIdToHousingMap = new HashMap();

    /**
     * Maven session reference.
     */
    private MavenSession mavenSession;

    /**
     * Plugin cache manager.
     */
    private final PluginCacheManager cacheManager = new PluginCacheManager();

    /**
     * Goal to Plugins mapper.
     */
    private GoalToJellyScriptHousingMapper mapper = new GoalToJellyScriptHousingMapper();

    /**
     * Current plugins mapper (transient - includes maven.xml, etc). *
     */
    private GoalToJellyScriptHousingMapper transientMapper = mapper;

    /**
     * Plugins to be popped afterwards.
     */
    private Set delayedPops = new HashSet();

    /**
     * The current goal attainment base context.
     */
    private MavenJellyContext baseContext;

    private static final String PLUGIN_TEMP_MAP = "PluginManager.PLUGIN_TEMP_MAP";

    /**
     * Default constructor.
     *
     * @param session The MavenSession this plugin manager will use
     *                until Maven shuts down.
     */
    public PluginManager( MavenSession session )
    {
        mavenSession = session;
    }

    /**
     * Get the list of plugin files.
     */
    private Map getPluginFiles( File directory, boolean acceptDirectories )
    {
        File[] files = directory.listFiles();
        if ( files == null )
        {
            return Collections.EMPTY_MAP;
        }

        Map pluginFiles = new HashMap();
        for ( int i = 0; i < files.length; i++ )
        {
            String plugin = files[i].getName();
            if ( files[i].isDirectory() && acceptDirectories )
            {
                pluginFiles.put( plugin, files[i] );
            }
            else
            {
                int index = plugin.indexOf( ".jar" );
                if ( index >= 0 )
                {
                    String name = plugin.substring( 0, index );
                    pluginFiles.put( name, files[i] );
                }
            }
        }
        return pluginFiles;
    }

    /**
     * Load plugins.
     *
     * @throws MavenException when the plugin jars can't be expanded
     */
    private void loadUncachedPlugins( Map pluginFiles )
        throws IOException, MavenException
    {
        LOGGER.debug( "Now loading uncached plugins" );

        for ( Iterator i = pluginFiles.keySet().iterator(); i.hasNext(); )
        {
            String name = (String) i.next();
            File pluginDir = (File) pluginFiles.get( name );

            if ( !isLoaded( name ) )
            {
                JellyScriptHousing housing = createPluginHousing( pluginDir );
                if ( housing != null )
                {
                    cacheManager.registerPlugin( name, housing );
                    housing.parse( cacheManager );
                    housing.parse( mapper );
                }
            }
        }
    }

    /**
     * Initialize all plugins.
     */
    public void initialize()
        throws IOException, MavenException
    {
        if ( LOGGER.isDebugEnabled() )
        {
            LOGGER.debug( "Initializing Plugins!" );
        }

        setPluginsDir( new File( mavenSession.getRootContext().getPluginsDir() ) );
        setUnpackedPluginsDir( new File( mavenSession.getRootContext().getUnpackedPluginsDir() ) );
        setUserPluginsDir( new File( mavenSession.getRootContext().getUserPluginsDir() ) );

        if ( !getPluginsDir().isDirectory()
            || ( ( getPluginsDir().listFiles() != null ) && ( getPluginsDir().listFiles().length == 0 ) ) )
        {
            throw new MavenException( "Maven was badly installed. Please reinstall it." );
        }

        if ( LOGGER.isDebugEnabled() )
        {
            LOGGER.debug( "Set plugin source directory to " + getPluginsDir().getAbsolutePath() );
            LOGGER.debug( "Set unpacked plugin directory to " + getUnpackedPluginsDir().getAbsolutePath() );
            LOGGER.debug( "Set user plugin directory to " + getUserPluginsDir().getAbsolutePath() );
        }

        verifyUnpackedPluginsDir();

        // plugin profile at this point is all of the JAR files in the plugins directory and the unpacked plugins dir
        // future ideas: have installation, user (outside of the cache), and project (via deps) plugins
        // allow further customisation via a profile descriptor.

        Map pluginFiles = getPluginFiles( pluginsDir, true );
        Map userPluginFiles = getPluginFiles( userPluginsDir, false );

        if ( !userPluginFiles.isEmpty() )
        {
            pluginFiles.putAll( userPluginFiles );
        }

        cacheManager.loadCache( unpackedPluginsDir );

        Map pluginDirs = expandPluginFiles( pluginFiles );

        LOGGER.debug( "Now mapping cached plugins" );
        if ( !cacheManager.mapPlugins( mapper, this, pluginDirs ) )
        {
            LOGGER.info( "Cache invalidated due to out of date plugins" );
            // The following housings are considered loaded - so go through and cache them manually
            for ( Iterator i = pluginHousings.values().iterator(); i.hasNext(); )
            {
                JellyScriptHousing housing = (JellyScriptHousing) i.next();
                cacheManager.registerPlugin( housing.getName(), housing );
                housing.parse( cacheManager );
                housing.parse( mapper );
            }
        }

        loadUncachedPlugins( pluginDirs );

        cacheManager.saveCache( unpackedPluginsDir );

        LOGGER.debug( "Finished initializing Plugins!" );
    }

    private Map expandPluginFiles( Map pluginFiles )
        throws MavenException
    {
        Map pluginDirs = new HashMap();
        for ( Iterator i = pluginFiles.keySet().iterator(); i.hasNext(); )
        {
            String name = (String) i.next();
            File jarFile = (File) pluginFiles.get( name );
            File dir = jarFile;
            if ( !dir.isDirectory() )
            {
                dir = unpackPlugin( name, jarFile, true );
            }
            pluginDirs.put( name, dir );
        }
        return pluginDirs;
    }

    JellyScriptHousing loadPluginHousing( String name, File pluginDir )
        throws IOException
    {
        JellyScriptHousing housing = (JellyScriptHousing) pluginHousings.get( name );
        return ( housing == null ? createLazyPluginHousing( pluginDir ) : housing );
    }

    private JellyScriptHousing createPluginHousing( File pluginDir )
        throws MavenException, IOException
    {
        JellyScriptHousing housing = createLazyPluginHousing( pluginDir );
        if ( housing != null )
        {
            String artifactId = housing.getProject().getArtifactId();
            mapArtifactIdToPluginHousing( artifactId, housing );
        }
        return housing;
    }

    void mapArtifactIdToPluginHousing( String artifactId, JellyScriptHousing housing )
    {
        if ( artifactIdToHousingMap.containsKey( artifactId ) )
        {
            JellyScriptHousing h = (JellyScriptHousing) artifactIdToHousingMap.get( artifactId );
            LOGGER.warn( "WARNING: Plugin '" + artifactId + "' is already loaded from " + h.getName()
                + "; attempting to load " + housing.getName() );
        }
        artifactIdToHousingMap.put( artifactId, housing );
    }

    private JellyScriptHousing createLazyPluginHousing( File pluginDir )
        throws IOException
    {
        if ( !pluginDir.isDirectory() || !new File( pluginDir, "project.xml" ).exists() )
        {
            LOGGER.debug( "Not a plugin directory: " + pluginDir );
            return null;
        }

        String pluginName = pluginDir.getName();

        LOGGER.debug( "Loading plugin '" + pluginName + "'" );

        JellyScriptHousing jellyScriptHousing = new JellyScriptHousing( pluginDir, mavenSession.getRootContext() );

        pluginHousings.put( pluginName, jellyScriptHousing );

        return jellyScriptHousing;
    }

    private boolean isLoaded( String name )
    {
        return pluginHousings.containsKey( name );
    }

    /**
     * @param project
     * @param jelly
     * @param systemId the system identifier to help resolve relative URLs
     * @return JellyScriptHousing
     * @throws Exception
     * @deprecated get rid of this - it duplicates functionality in the housing
     * @todo refactor into housing
     * @todo don't throw Exception
     * @todo get rid of this - it duplicates functionality in the housing
     */
    private JellyScriptHousing createJellyScriptHousing( Project project, String systemId, InputStream jelly )
        throws Exception
    {
        JellyScriptHousing jellyScriptHousing = new JellyScriptHousing();

        try
        {
            // Now lets compile the script once so we can use it repeatedly.
            Script script = JellyUtils.compileScript( jelly, systemId, project.getContext() );

            jellyScriptHousing.setProject( project );
            jellyScriptHousing.setScript( script );
        }
        catch ( Exception e )
        {
            // FIXME: exception? Yuck!
            throw new MavenException( "Error parsing driver.jelly", e );
        }

        return jellyScriptHousing;
    }

    /**
     * @param project
     * @param jelly
     * @return JellyScriptHousing
     * @todo [1.0] into the housing?
     */
    private JellyScriptHousing createJellyScriptHousing( Project project, File jelly )
    {
        JellyScriptHousing jellyScriptHousing = new JellyScriptHousing();

        jellyScriptHousing.setProject( project );
        jellyScriptHousing.setSource( jelly );

        return jellyScriptHousing;
    }

    /**
     * Process the dependencies of the project, adding dependencies to the
     * appropriate classloader etc
     *
     * @throws MalformedURLException if a file can't be converted to a URL.
     * @throws Exception             for any other issue. FIXME
     */
    public void processDependencies( Project project )
        throws MalformedURLException, Exception
    {
        if ( ( project.getArtifacts() == null ) || project.getArtifacts().isEmpty() )
        {
            LOGGER.debug( "No dependencies to process for project " + project.getName() );
            return;
        }

        ClassLoader cl = project.getContext().getClassLoader();
        if ( !( cl instanceof ForeheadClassLoader ) )
        {
            return;
        }

        ForeheadClassLoader projectClassLoader = (ForeheadClassLoader) cl;
        LOGGER.debug( "Processing dependencies for project " + project.getName() + "; classloader " + projectClassLoader );

        // add the dependencies to the classpath
        for ( Iterator i = project.getArtifacts().iterator(); i.hasNext(); )
        {
            Artifact artifact = (Artifact) i.next();
            Dependency dependency = artifact.getDependency();
            if ( dependency.isPlugin() )
            {
                // TODO: is this the best place to call this?
                installPlugin( artifact.getFile(), project );
            }

            // get correct classloader
            String dependencyClassLoader = dependency.getProperty( "classloader" );

            // add to classloader
            if ( artifact.exists() )
            {
                // Only add compile type dependencies to classloader
                // what about ejbs etc
                if ( dependency.isAddedToClasspath() )
                {
                    if ( dependencyClassLoader != null )
                    {
                        LOGGER.debug( "DEPRECATION: " + dependency.getId() + " in project " + project.getId()
                            + " forces the classloader '" + dependencyClassLoader + "'" );
                        LOGGER.debug( "             This behaviour is deprecated. Please refer to the FAQ" );
                        ForeheadClassLoader loader = Forehead.getInstance().getClassLoader( dependencyClassLoader );
                        if ( loader == null )
                        {
                            LOGGER.warn( "classloader '" + dependencyClassLoader
                                + "' not found. Adding dependencies to the project classloader instead" );
                            loader = projectClassLoader;
                        }
                        else
                        {
                            LOGGER.debug( "poking dependency " + artifact.getFile() + " into classloader "
                                + dependencyClassLoader );
                        }
                        loader.addURL( artifact.getFile().toURL() );
                    }
                    else
                    {
                        LOGGER.debug( "adding dependency " + artifact.getFile() + " into project classloader" );
                        projectClassLoader.addURL( artifact.getFile().toURL() );
                    }
                }
                else
                {
                    LOGGER.debug( "Non classpath dependency: '" + artifact.getFile() + "' not added to classpath" );
                }
            }
            else
            {
                LOGGER.info( "Artifact '" + artifact.getFile() + "' not found to add to classpath" );
            }
        }
        // Explicity set the classloader used to find resources. As we just
        // poked all the dependencies into the classloader.
        project.getContext().setClassLoader( projectClassLoader );
    }

    List readMavenXml( Project project, GoalToJellyScriptHousingMapper mapper )
        throws MavenException
    {
        Project p = project;
        List projectHousings = new ArrayList();

        // Project's Jelly script
        while ( p != null )
        {
            if ( p.hasMavenXml() )
            {
                File mavenXml = p.getMavenXml();

                JellyScriptHousing jellyScriptHousing = createJellyScriptHousing( project, mavenXml );
                jellyScriptHousing.parse( mapper );
                projectHousings.add( jellyScriptHousing );
            }
            p = p.getParent();
        }
        return projectHousings;
    }

    MavenJellyContext setupBaseContext( Project project )
    {
        MavenJellyContext prevBaseContext = baseContext;
        baseContext = new MavenJellyContext( mavenSession.getRootContext() );
        baseContext.setInherit( true );
        JellyUtils.populateVariables( baseContext, project.getContext() );
        project.pushContext( baseContext );
        baseContext.setProject( project );

        return prevBaseContext;
    }

    /**
     * Attain the goals.
     *
     * @throws Exception If one of the specified
     *                   goals refers to an non-existent goal.
     * @throws Exception If an exception occurs while running a goal.
     * @todo stop throwing Exception
     */
    public void attainGoals( Project project, List goals )
        throws Exception
    {
        MavenJellyContext prevBaseContext = setupBaseContext( project );

        // Set up the ant project.
        AntProjectBuilder.build( project, baseContext );

        // TODO: shouldn't this be a stack too? Then session attribute not needed
        transientMapper = new GoalToJellyScriptHousingMapper();

        // Create the Jelly session
        Session session = new JellySession( baseContext.getXMLOutput() );
        session.setAttribute( BASE_CONTEXT, baseContext );
        session.setAttribute( PLUGIN_MANAGER, this );
        session.setAttribute( GOAL_MAPPER, transientMapper );

        // add the global session to the pluginContext so that it can be used by tags
        baseContext.setVariable( GLOBAL_SESSION_KEY, session );

        // Execution of the Jelly scripts:
        //
        // We run the Jelly scripts in the following order:
        //
        // 1) driver.jelly - should be removed
        // 2) project's maven.xml
        // 3) parent's maven.xml (if it exists)
        // 4) plugin.jelly
        //
        // The Maven version of the <goal/> Werkz tag has been constructed so that the first
        // definition of a goal wins.
        //
        // We run them in this order because we do not know before hand which plugin goals a project
        // may wish to override so we guarantee precedence of the goals by running the jelly scripts
        // in the above mentioned order.

        // driver.jelly
        InputStream driver = getClass().getResourceAsStream( "/driver.jelly" );
        JellyScriptHousing driverHousing = createJellyScriptHousing( project, getClass().getResource( "/driver.jelly" )
            .toString(), driver );
        // TODO: stop reading all scripts 2 times
        driver.close();
        driver = getClass().getResourceAsStream( "/driver.jelly" );
        driverHousing.parse( transientMapper, null, driver );
        driver.close();

        List projectHousings = readMavenXml( project, transientMapper );

        if ( goals != null )
        {
            for ( Iterator i = goals.iterator(); i.hasNext(); )
            {
                String goal = (String) i.next();
                if ( goal.trim().length() == 0 )
                {
                    i.remove();
                }
            }
        }

        // Default goal handling - if goals are null, don't even process the default
        String defaultGoalName = transientMapper.getDefaultGoalName();

        if ( ( project.getBuild() != null ) && ( project.getBuild().getDefaultGoal() != null ) )
        {
            defaultGoalName = project.getBuild().getDefaultGoal();
        }

        if ( defaultGoalName != null )
        {
            // By evaluating expression now it has the same scope as the POM.
            Expression e = JellyUtils.decomposeExpression( defaultGoalName, baseContext );
            defaultGoalName = e.evaluateAsString( baseContext );
            baseContext.setVariable( MavenConstants.DEFAULT_GOAL, defaultGoalName );

            if ( ( goals != null ) && ( goals.size() == 0 ) )
            {
                LOGGER.debug( "Using default goal: " + defaultGoalName );
                goals.add( defaultGoalName );
            }
        }
        if ( goals == null )
        {
            // So the reactor can process projects but not run any goals
            goals = Collections.EMPTY_LIST;
        }
        else
        {
            // Always run build:start and build:end
            goals.add( 0, "build:start" );
            goals.add( "build:end" );
        }

        transientMapper.merge( mapper );

        WerkzProject werkzProject = new WerkzProject();
        baseContext.setWerkzProject( werkzProject );

        Set pluginSet = new HashSet();
        // poor mans stack - only pop when you finish the same frame that the plugin was lazily init in
        Set oldDelayedPops = new HashSet( delayedPops );
        delayedPops.clear();

        Thread.currentThread().setContextClassLoader( null );

        try
        {
            runScript( driverHousing, baseContext );
            transientMapper.addResolvedPlugins( Collections.singletonList( driverHousing ) );

            // Dependencies must be processed after the driver is run for compatibility
            // FIXME: From attainGoals angle, how does it know the project needs to
            //        to be verified, or that the project object hasn't been used before
            project.verifyDependencies();
            processDependencies( project );

            for ( Iterator j = projectHousings.iterator(); j.hasNext(); )
            {
                JellyScriptHousing housing = (JellyScriptHousing) j.next();
                runScript( housing, baseContext );
            }
            transientMapper.addResolvedPlugins( projectHousings );

            // Plugin Jelly scripts
            for ( Iterator i = goals.iterator(); i.hasNext(); )
            {
                String goalName = (String) i.next();

                pluginSet.addAll( prepAttainGoal( goalName, baseContext, transientMapper ) );
            }

            // Plugin Jelly scripts
            for ( Iterator i = goals.iterator(); i.hasNext(); )
            {
                String goalName = (String) i.next();
                LOGGER.debug( "attaining goal " + goalName );
                try
                {
                    Goal goal = werkzProject.getGoal( goalName );
                    if ( ( goal == null ) || ( goal.getAction() == null ) )
                    {
                        throw new NoSuchGoalException( goalName );
                    }
                    goal.attain( session );
                }
                catch ( NoSuchGoalException e )
                {
                    throw new UnknownGoalException( goalName );
                }
            }
        }
        finally
        {
            cleanupAttainGoal( pluginSet );
            delayedPops = oldDelayedPops;
            reinstallPlugins( project.getContext() );
            project.popContext();
            baseContext = prevBaseContext;
        }
    }

    /**
     * @todo don't throw Exception
     */
    public void cleanupAttainGoal( Set pluginSet )
        throws Exception
    {
        delayedPops.addAll( pluginSet );

        for ( Iterator j = delayedPops.iterator(); j.hasNext(); )
        {
            JellyScriptHousing housing = (JellyScriptHousing) j.next();

            reinstallPlugins( housing.getProject().getContext() );

            housing.getProject().popContext();
        }
        delayedPops.clear();
    }

    /**
     * Use the name of a goal to lookup all the plugins (that are stored in the plugin housings) that need to be
     * executed in order to satisfy all the required preconditions for successful goal attainment.
     *
     * @param goalName    the goal
     * @param baseContext the base context to attain in
     * @return a set of plugins required to attain the goal
     * @throws Exception
     * @todo don't throw Exception
     */
    public Set prepAttainGoal( String goalName, MavenJellyContext baseContext, GoalToJellyScriptHousingMapper goalMapper )
        throws Exception
    {
        Set pluginSet = goalMapper.resolveJellyScriptHousings( goalName );

        for ( Iterator j = pluginSet.iterator(); j.hasNext(); )
        {
            JellyScriptHousing housing = (JellyScriptHousing) j.next();
            initialiseHousingPluginContext( housing, baseContext );
        }
        return pluginSet;
    }

    private MavenJellyContext initialiseHousingPluginContext( JellyScriptHousing housing, MavenJellyContext baseContext )
        throws Exception
    {
        Project project = housing.getProject();

        // TODO: I think this should merge into pluginContext, but that seems to break existing jelly as it depends on
        // that kind of warped scoping. Fix this in 1.1
        MavenUtils.integrateMapInContext( housing.getPluginProperties(), baseContext );

        /*
         // Instead, we must go through each property and merge from previous plugin contexts, or the original property
         // first integrate new ones
         MavenUtils.integrateMapInContext( project.getContext().getVariables(), baseContext );
         // now integrate properties that have a new value
         Properties p = new Properties();
         for ( Iterator i = housing.getPluginProperties().keySet().iterator(); i.hasNext(); )
         {
         String key = (String) i.next();
         Object value = project.getContext().getVariable( key );
         if ( value != null )
         {
         baseContext.setVariable( key, value );
         }
         else
         {
         p.setProperty( key, (String) housing.getPluginProperties().get( key ) );
         }
         }
         MavenUtils.integrateMapInContext( p, baseContext );
         // but leave alone anything in there that is not a plugin property, and already exists in baseContext
         */
        // TODO necessary to create a new one every time?
        MavenJellyContext pluginContext = new MavenJellyContext( baseContext );
        project.pushContext( pluginContext );
        pluginContext.setInherit( true );
        pluginContext.setVariable( "context", pluginContext );
        pluginContext.setVariable( "plugin", project );
        pluginContext.setVariable( "plugin.dir", housing.getPluginDirectory() );
        pluginContext.setVariable( "plugin.resources", new File( housing.getPluginDirectory(), "plugin-resources" ) );

        LOGGER.debug( "initialising plugin housing: " + project );
        runScript( housing, pluginContext );

        return pluginContext;
    }

    /**
     * Sets the pluginsDir attribute of the PluginManager object
     *
     * @param dir The maven plugin directory.
     */
    private void setPluginsDir( File dir )
    {
        pluginsDir = dir;
    }

    /**
     * Retrieve the directory containing all plugins.
     *
     * @return The directory containing all plugins.
     */
    private File getPluginsDir()
    {
        return pluginsDir;
    }

    /**
     * Sets the directory where the users plugins are located.
     *
     * @param dir The directory where the users plugins are located.
     */
    private void setUserPluginsDir( File dir )
    {
        userPluginsDir = dir;
    }

    /**
     * Gets the directory where the user plugins are located.
     *
     * @return the directory where the user plugins are located.
     */
    private File getUserPluginsDir()
    {
        return userPluginsDir;
    }

    /**
     * Sets the directory where the unpacked plugins are located.
     *
     * @param dir The directory where the unpacked plugins are located.
     */
    private void setUnpackedPluginsDir( File dir )
    {
        unpackedPluginsDir = dir;
    }

    /**
     * Sets the directory where the unpacked plugins are located.
     *
     * @return the directory where the unpacked plugins are located.
     */
    private File getUnpackedPluginsDir()
    {
        return unpackedPluginsDir;
    }

    /**
     * @return Set
     */
    public Set getGoalNames()
    {
        return mapper.getGoalNames();
    }

    /**
     * Warning - this completely scrogs the default mapper. Only use this before System.exit!
     * (currently used by maven -u).
     *
     * @return Set
     * @todo refactor to return mapper instead and use that, or perhaps instantiate a new plugin manager
     */
    public Set getGoalNames( Project project )
        throws MavenException
    {
        mapper = new GoalToJellyScriptHousingMapper();
        readMavenXml( project, mapper );
        return mapper.getGoalNames();
    }

    /**
     */
    public void installPlugin( File file, Project parentProject )
        throws MavenException
    {
        // By default, don't copy to the unpacked plugins directory - only use this dependency for this project
        installPlugin( file, parentProject, false );
    }

    /**
     * Load and install a plugin.
     *
     * @param file          the file to install. Must be a plugin jar
     * @param parentProject the project to load the installed plugin into
     */
    public void installPlugin( File file, Project parentProject, boolean cache )
        throws MavenException
    {
        LOGGER.debug( "Using plugin file: " + file );
        try
        {
            String pluginName = file.getCanonicalFile().getName();
            pluginName = pluginName.substring( 0, pluginName.indexOf( ".jar" ) );

            if ( isLoaded( pluginName ) )
            {
                // already installed this version
                return;
            }

            // expand it
            File unpackedPluginDir = unpackPlugin( pluginName, file, cache );
            if ( unpackedPluginDir != null )
            {
                JellyScriptHousing housing = createLazyPluginHousing( unpackedPluginDir );
                if ( housing != null )
                {
                    String artifactId = housing.getProject().getArtifactId();
                    if ( artifactIdToHousingMap.containsKey( artifactId ) )
                    {
                        // old version
                        JellyScriptHousing oldHousing = (JellyScriptHousing) artifactIdToHousingMap.get( artifactId );
                        LOGGER.debug( "Temporarily uninstalling: " + oldHousing );
                        addPluginToReinstall( parentProject.getContext(), artifactId, oldHousing );
                        pluginHousings.remove( oldHousing.getName() );
                        mapper.invalidatePlugin( oldHousing );
                        transientMapper.invalidatePlugin( oldHousing );
                        artifactIdToHousingMap.remove( artifactId );
                    }

                    mapArtifactIdToPluginHousing( artifactId, housing );
                }
                else
                {
                    throw new MavenException( "Not a valid plugin file: " + file );
                }

                LOGGER.debug( "Installing plugin: " + housing );
                // By default, not caching the plugin - its a per execution installation
                housing.parse( transientMapper );
                // Should only be putting in the transientMapper - but it is not consistent with isLoaded
                housing.parse( mapper );
                if ( cache )
                {
                    FileUtils.copyFileToDirectory( file, userPluginsDir );
                    cacheManager.registerPlugin( pluginName, housing );
                    housing.parse( cacheManager );
                    cacheManager.saveCache( unpackedPluginsDir );
                }
            }
            else
            {
                throw new MavenException( "Not a valid JAR file: " + file );
            }
        }
        catch ( IOException e )
        {
            throw new MavenException( "Error installing plugin", e );
        }
    }

    /**
     * @todo can this be removed and simplified if transient mapper contains the plugin mapping only?
     */
    private void addPluginToReinstall( MavenJellyContext context, String artifactId, JellyScriptHousing housing )
    {
        Map m = (Map) context.getVariables().get( PLUGIN_TEMP_MAP );
        if ( m == null )
        {
            m = new HashMap();
            context.setVariable( PLUGIN_TEMP_MAP, m );
        }
        m.put( artifactId, housing );
    }

    /**
     * @todo can this be removed and simplified if transient mapper contains the plugin mapping only?
     */
    private void reinstallPlugins( MavenJellyContext context )
        throws MavenException
    {
        Map m = (Map) context.getVariables().get( PLUGIN_TEMP_MAP );
        if ( m != null )
        {
            for ( Iterator i = m.keySet().iterator(); i.hasNext(); )
            {
                String artifactId = (String) i.next();
                JellyScriptHousing housing = (JellyScriptHousing) m.get( artifactId );
                pluginHousings.remove( housing.getName() );
                mapper.invalidatePlugin( housing );
                transientMapper.invalidatePlugin( housing );
                housing = (JellyScriptHousing) m.get( artifactId );
                LOGGER.debug( "Reinstalling: " + housing );
                housing.parse( transientMapper );
                housing.parse( mapper );
            }
        }
    }

    public void uninstallPlugin( String artifactId )
        throws IOException
    {
        LOGGER.debug( "Uninstalling plugin: " + artifactId );

        JellyScriptHousing housing = (JellyScriptHousing) artifactIdToHousingMap.get( artifactId );
        if ( housing == null )
        {
            LOGGER.warn( "Plugin not found when attempting to uninstall '" + artifactId + "'" );
            return;
        }

        String name = housing.getName();
        pluginHousings.remove( name );
        cacheManager.invalidateCache( name );
        mapper.invalidatePlugin( housing );
        transientMapper.invalidatePlugin( housing );
        artifactIdToHousingMap.remove( artifactId );
        cacheManager.saveCache( unpackedPluginsDir );
    }

    /**
     * @param id
     * @return MavenJellyContext
     * @throws UnknownPluginException
     * @todo [1.0] refactor out, or make more appropriate structure
     * @todo remove throws Exception
     */
    public MavenJellyContext getPluginContext( String id )
        throws MavenException, UnknownPluginException
    {
        JellyScriptHousing housing = (JellyScriptHousing) artifactIdToHousingMap.get( id );
        if ( housing != null )
        {
            Project project = housing.getProject();
            if ( baseContext.equals( project.getContext().getParent() ) )
            {
                LOGGER.debug( "Plugin context for " + id + " already initialised for this base context" );
                return project.getContext();
            }
            else
            {
                LOGGER.debug( "Plugin context for " + id
                    + " not initialised for this base context: initialising inside getPluginContext" );
                try
                {
                    return initialiseHousingPluginContext( housing, baseContext );
                }
                catch ( Exception e )
                {
                    throw new MavenException( "Error initialising plugin context", e );
                }
            }
        }
        throw new UnknownPluginException( id );
    }

    public String getGoalDescription( String goalName )
    {
        return mapper.getGoalDescription( goalName );
    }

    public void addDelayedPops( Set set )
    {
        delayedPops.addAll( set );
    }

    /**
     * Unpack the plugin.
     *
     * @throws MavenException if there was a problem unpacking
     */
    File unpackPlugin( String pluginName, File jarFile, boolean cache )
        throws MavenException
    {
        File unzipDir = new File( unpackedPluginsDir, pluginName );

        // if there's no directory, or the jar is newer, expand the jar
        boolean exists = unzipDir.exists();
        if ( !exists || ( jarFile.lastModified() > unzipDir.lastModified() ) )
        {
            if ( LOGGER.isDebugEnabled() )
            {
                LOGGER.debug( "Unpacking " + jarFile.getName() + " to directory --> " + unzipDir.getAbsolutePath() );
            }

            if ( cache )
            {
                cacheManager.invalidateCache( pluginName );
            }

            try
            {
                if ( exists )
                {
                    FileUtils.deleteDirectory( unzipDir );
                }

                Expand unzipper = new Expand();
                unzipper.setSrc( jarFile );
                unzipper.setDest( unzipDir );
                unzipper.execute();
            }
            catch ( IOException e )
            {
                throw new MavenException( "Unable to extract plugin: " + jarFile, e );
            }
        }
        return unzipDir;
    }

    /**
     * @return Script
     * @todo get rid of throws Exception
     */
    private Script loadScript( JellyScriptHousing jellyScriptHousing )
        throws Exception
    {
        // TODO [1.0]: this currently duplicates createJellyScriptHousing for others - it is the lazy version

        // We will add the plugin classes to the plugin class loader.
        if ( jellyScriptHousing.getPluginDirectory() != null )
        {
            // not needed for maven.xml
            // TODO: should differentiate between plugins and script housings better
            jellyScriptHousing.getProject().verifyDependencies();
            processDependencies( jellyScriptHousing.getProject() );
            ClassLoader cl = jellyScriptHousing.getProject().getContext().getClassLoader();
            if ( cl instanceof ForeheadClassLoader )
            {
                ForeheadClassLoader pluginClassLoader = (ForeheadClassLoader) cl;
                pluginClassLoader.addURL( jellyScriptHousing.getPluginDirectory().toURL() );
            }
        }

        MavenJellyContext context = jellyScriptHousing.getProject().getContext();
        URL oldRoot = context.getRootURL();
        URL oldCurrent = context.getCurrentURL();

        context.setRootURL( jellyScriptHousing.getSource().toURL() );
        context.setCurrentURL( jellyScriptHousing.getSource().toURL() );

        try
        {
            if ( LOGGER.isDebugEnabled() )
            {
                LOGGER.debug( "Jelly script to parse : " + jellyScriptHousing.getSource().toURL() );
            }
            Script script = JellyUtils.compileScript( jellyScriptHousing.getSource(), context );

            context.setRootURL( oldRoot );
            context.setCurrentURL( oldCurrent );

            return script;
        }
        catch ( Exception e )
        {
            // FIXME: exception? Yuck!
            throw new MavenException( "Error parsing: " + jellyScriptHousing.getSource(), e );
        }
    }

    /**
     * @param context
     * @throws Exception
     * @todo get rid of throws Exception
     */
    void runScript( JellyScriptHousing jellyScriptHousing, MavenJellyContext context )
        throws Exception
    {
        LOGGER.debug( "running script " + jellyScriptHousing.getSource() );

        Script s = jellyScriptHousing.getScript();
        if ( s == null )
        {
            s = loadScript( jellyScriptHousing );
            jellyScriptHousing.setScript( s );
        }
        if ( context.getVariable( PLUGIN_HOUSING ) != null )
        {
            throw new IllegalStateException( "nested plugin housings" );
        }
        context.setVariable( PLUGIN_HOUSING, jellyScriptHousing );
        s.run( context, context.getXMLOutput() );
        context.removeVariable( PLUGIN_HOUSING );
    }

    public Project getPluginProjectFromGoal( String goal )
        throws MavenException
    {
        JellyScriptHousing housing = mapper.getPluginHousing( goal );
        return housing != null ? housing.getProject() : null;
    }

    public Collection getPluginList()
    {
        ArrayList list = new ArrayList();
        for ( Iterator i = pluginHousings.values().iterator(); i.hasNext(); )
        {
            JellyScriptHousing housing = (JellyScriptHousing) i.next();
            list.add( housing.getName() );
        }
        Collections.sort( list );
        return list;
    }

    private void verifyUnpackedPluginsDir()
        throws MavenException
    {
        if ( !unpackedPluginsDir.exists() )
        {
            LOGGER.warn( getMessage( "directory.nonexistant.warning", unpackedPluginsDir ) );

            if ( !unpackedPluginsDir.mkdirs() )
            {
                throw new MavenException( getMessage( "cannot.create.directory.warning", unpackedPluginsDir ) );
            }
        }

        if ( !unpackedPluginsDir.isDirectory() )
        {
            throw new MavenException( getMessage( "not.directory.warning", unpackedPluginsDir ) );
        }

        if ( !unpackedPluginsDir.canWrite() )
        {
            throw new MavenException( getMessage( "not.writable.warning", unpackedPluginsDir ) );
        }
    }

}
TOP

Related Classes of org.apache.maven.plugin.PluginManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.