dion 2003/08/18 21:28:50 Modified: src/java/org/apache/maven/plugin PluginManager.java Added: src/java/org/apache/maven/plugin PluginCacheManager.java Removed: src/java/org/apache/maven/plugin JellyScriptHousing.java GoalToJellyScriptHousingMapper.java JellyPlugin.java Log: Switch stable branch back to HEAD Revision Changes Path 1.63 +655 -518 maven/src/java/org/apache/maven/plugin/PluginManager.java Index: PluginManager.java =================================================================== RCS file: /home/cvs/maven/src/java/org/apache/maven/plugin/PluginManager.java,v retrieving revision 1.62 retrieving revision 1.63 diff -u -r1.62 -r1.63 --- PluginManager.java 4 Aug 2003 06:27:56 -0000 1.62 +++ PluginManager.java 19 Aug 2003 04:28:50 -0000 1.63 @@ -57,51 +57,67 @@ */ import com.werken.forehead.Forehead; -import com.werken.forehead.ForeheadClassLoader; import com.werken.werkz.Goal; import com.werken.werkz.Session; -import com.werken.werkz.WerkzProject; import com.werken.werkz.jelly.JellySession; -import org.apache.commons.grant.GrantProject; -import org.apache.commons.jelly.Script; -import org.apache.commons.jelly.XMLOutput; -import org.apache.commons.jelly.tags.ant.AntTagLibrary; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.maven.AbstractMavenComponent; import org.apache.maven.GoalException; -import org.apache.maven.Maven; import org.apache.maven.MavenConstants; import org.apache.maven.MavenException; -import org.apache.maven.jelly.JellyBuildListener; -import org.apache.maven.jelly.JellyPropsHandler; +import org.apache.maven.MavenSession; +import org.apache.maven.MavenUtils; +import org.apache.maven.NoGoalException; +import org.apache.maven.UnknownGoalException; import org.apache.maven.jelly.JellyUtils; import org.apache.maven.jelly.MavenJellyContext; -import org.apache.maven.jelly.tags.werkz.MavenAttainGoalListener; -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.tools.ant.types.Path; import java.io.File; import java.io.FileInputStream; -import java.io.FileReader; -import java.io.InputStream; -import java.io.OutputStreamWriter; -import java.io.PrintStream; -import java.io.Writer; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.HashMap; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; -import java.util.Map; +import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; +/* + +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 Maven. <p> + * Plugin manager for MavenSession. <p> * * The <code>PluginManager</code> deals with all aspects of a plugins lifecycle. * </p> @@ -111,27 +127,24 @@ * * @version $Id$ * - * @todo Goal inheritance */ public class PluginManager extends AbstractMavenComponent { + /** Plug-in main script name. */ + public static final String PLUGIN_SCRIPT_NAME = "plugin.jelly"; + + /** Plug-in default properties name. */ + public static final String PLUGIN_PROPERTIES_NAME = "plugin.properties"; + /** Logger */ private static final Log log = LogFactory.getLog( PluginManager.class ); - /** */ - public static final String PLUGIN_MANAGER = "maven.pluginManager"; - /** */ - public static final String BASE_CONTEXT = "maven.goalAttainmentContext"; - /** */ - public static String JELLY_CONTEXT = "mavenGoalTag.jellyContext"; - /** */ - public static String XML_OUTPUT = "mavenGoalTag.xmlOutput"; /** * The variable key that holds an implementation of the - * <code>com.werken.werkz.Session</code> in the parent scope pluginContext. + * <code>com.werken.werkz.Session</code> in the parent scope context. */ - public static final String GLOBAL_SESSION_KEY = "maven.session.global"; + private static final String GLOBAL_SESSION_KEY = "maven.session.global"; /** The directory where plugin jars reside under Maven's home. */ private File pluginsDir; @@ -139,31 +152,23 @@ /** The directory where the plugin jars are unpacked to. */ private File unpackedPluginsDir; + /** + * This contains a set of sets where each set contains a list of + * plugins that have been loaded for a particular project. + */ + private Set loadedPlugins; + /** Is the plugin manager initialized. */ private boolean initialized = false; /** Maven session reference. */ - private Maven maven; - - private Map jellyScriptHousings; - - /** map of loaded plugins by id */ - private Map loadedPlugins; - - /** map of plugins by id */ - private Map plugins; - - /** rootClassLoader classloader. */ - private ForeheadClassLoader rootClassLoader; + private MavenSession mavenSession; - /** maven.rootClassLoader classloader. */ - private ForeheadClassLoader mavenRootClassLoader; + /** Plugin cache manager. */ + private PluginCacheManager cacheManager; - /** Goal to Plugins mapper. */ - private GoalToJellyScriptHousingMapper mapper; - - /** Context in which to compile Jelly scripts. */ - private MavenJellyContext compileScriptContext; + /** Cache manager for user jelly scripts. */ + private PluginCacheManager transientCacheManager; /** * Default constructor. @@ -171,150 +176,50 @@ * @param mavenSession The MavenSession this plugin manager will use * until Maven shuts down. */ - public PluginManager( Maven mavenSession ) + public PluginManager( MavenSession mavenSession ) { - this.maven = mavenSession; - - jellyScriptHousings = new HashMap(); - loadedPlugins = new HashMap(); - plugins = new HashMap(); - - mapper = new GoalToJellyScriptHousingMapper(); + this.mavenSession = mavenSession; - // FIXME: Why are there two hard coded class loaders? - rootClassLoader = Forehead.getInstance().getClassLoader( "root" ); - mavenRootClassLoader = Forehead.getInstance().getClassLoader( "root.maven" ); - - compileScriptContext = new MavenJellyContext(); + loadedPlugins = new HashSet(); + cacheManager = new PluginCacheManager( true ); + transientCacheManager = new PluginCacheManager( true ); } // ---------------------------------------------------------------------- - // Accessors + // A C C E S S O R S // ---------------------------------------------------------------------- - private Maven getMaven() - { - return maven; - } - - private void setMaven( Maven maven ) - { - this.maven = maven; - } - - /** - * Expand the plugin jars if needed - * @throws MavenException - */ - private void expandPlugins() throws MavenException - { - File[] files = getPluginsDir().listFiles(); - - // First we expand any JARs that contain plugins to the unpack directory. - // This will be a different directory than the plugin jars if - // MAVEN_HOME_LOCAL / maven.home.local was set to a different directory - // than MAVEN_HOME / maven.home. - Expand unzipper = new Expand(); - for ( int i = 0; i < files.length; ++i ) - { - processPluginFile(unzipper, files[i]); - } - } - /** - * Load all plugins in the unpacked plugins dir - * @throws Exception + * Retrieve the set of all goal names. + * + * @return The set of <code>String</code> goal names. */ - private void loadPlugins() throws Exception - { - File[] files = getUnpackedPluginsDir().listFiles(); - - // Process each of the directorties. - for ( int i = 0; i < files.length; ++i ) - { - if ( files[i].isDirectory() ) - { - String pluginId = files[i].getName(); - if ( !isLoaded( pluginId ) ) - { - loadPlugin( pluginId ); - } - } - } - } - - private void processPluginFile(Expand unzipper, File pluginFile) - throws MavenException + public Set getGoalNames() { - // Only unpack the JAR if it hasn't been already, or is newer - // than the plugin directory - if ( pluginFile.getName().endsWith( ".jar" ) ) - { - // this is where JellyPlugins should be determined and created - JellyPlugin plugin = new JellyPlugin(); - plugin.setJarFile( pluginFile ); - String directory = pluginFile.getName(); - directory = directory.substring( 0, directory.indexOf( ".jar" ) ); - plugin.setId( directory ); - plugin.setUnpackDirectory( getUnpackedPluginsDir() ); - plugin.unpack(); - plugins.put(plugin.getId(), plugin); - } + return cacheManager.getGoalCache().keySet(); } /** - * Load the specified plugin. + * Retrieve a goal description by the goal name. * - * @param name The name of the plugin to load. + * @param name The goal name. * - * @throws Exception If an error occurs while initializing the plugin. + * @return The description or <code>null</code> if no + * description has been set. */ - private void loadPlugin( String name ) - throws Exception + public String getGoalDescription( String name ) { - System.err.println( "Loading plugin '" + name + "'" ); - JellyPlugin plugin = (JellyPlugin)plugins.get(name); - if (plugin == null) - { - plugin = new JellyPlugin(); - plugin.setId(name); - } - - plugin.setUnpackDirectory( getUnpackedPluginsDir() ); - plugin.setParentClassLoader( mavenRootClassLoader ); - Project pluginProject = getMaven().getProject( plugin.getDescriptor() , false ); - plugin.setProject( pluginProject ); - plugin.processDependencies(); + String value = cacheManager.getGoalCache().getProperty( name ); - if ( plugin.hasScript() ) + // Must check for "null" in case it was loaded from cache that way + if ( value == null || value.startsWith("null>")) { - JellyScriptHousing jellyScriptHousing = null; - try - { - // Create the PluginHousing - jellyScriptHousing = createJellyScriptHousing( plugin ); - } - catch ( Exception e ) - { - System.err.println( "Error loading plugin '" + name + "'" ); - throw e; - } - - // Store the plugin housing for future use. - jellyScriptHousings.put( name, jellyScriptHousing ); - mapper.parse( new FileReader( plugin.getScriptFile() ), jellyScriptHousing ); + return null; } - loadedPlugins.put(plugin.getId(), plugin); - } + String description = value.substring( 0, value.indexOf( ">" ) ); - /** - * @todo Why is this method public? - * @return - */ - public GoalToJellyScriptHousingMapper getMapper() - { - return mapper; + return description; } // ---------------------------------------------------------------------- @@ -325,7 +230,6 @@ * Initialize all plugins. * * @throws Exception If an error occurs while initializing any plugin. - * @todo why is this method public? */ public void initialize() throws Exception @@ -335,487 +239,646 @@ return; } - setPluginsDir( new File( getMaven().getProperty( MavenConstants.MAVEN_HOME ), "plugins" ) ); - setUnpackedPluginsDir( new File( maven.getProperty( MavenConstants.MAVEN_UNPACKED_PLUGINS_DIR ) ) ); + if( log.isDebugEnabled() ) + { + log.debug( "Initializing Plugins!" ); + } + setPluginsDir( new File( mavenSession.getRootContext().getMavenHome(), "plugins" ) ); + setUnpackedPluginsDir( new File( mavenSession.getRootContext().getUnpackedPluginsDir() ) ); + if( log.isDebugEnabled() ) + { + log.debug( "Set plugin source directory to " + + getPluginsDir().getAbsolutePath() ); + log.debug( "Set plugin cache directory to " + + getUnpackedPluginsDir().getAbsolutePath() ); + } + cacheManager.loadCache(); + + if( log.isDebugEnabled() ) + { + log.debug( "Unpacking plugins from directory --> " + + getPluginsDir().getAbsolutePath() ); + } - expandPlugins(); + expandPluginJars(); - loadPlugins(); + cachePlugins(); initialized = true; - log.info( "Finished initializing Plugins!" ); + log.debug( "Finished initializing Plugins!" ); } /** + * For any plugin that is not already cached according to the cache manager + * we cache it. + * @throws Exception FIXME. When anything goes wrong + */ + private void cachePlugins() throws Exception + { + // We need to get the directory listing again so that we + // can process plugins that were just unpacked by the + // above process. This time we're looking at the unpacked plugins. + File[] files = getUnpackedPluginsDir().listFiles(); + + if (log.isDebugEnabled()) + { + log.debug( "Processing unpacked plugins in " + + getUnpackedPluginsDir().getAbsolutePath() ); + } + + // Process each of the directorties. + for ( int i = 0; i < files.length; ++i ) + { + if ( files[i].isDirectory() ) + { + String directory = files[i].getName(); + + // If we haven't cached (or previous cache data has become invalid) + // the plugin, then do so now. + if ( isCached( directory ) == false ) + { + try + { + cachePlugin( files[i].getName() ); + } + catch ( Exception e ) + { + log.error( getMessage( "plugin.loading.error", files[i].getName() ) ); + e.printStackTrace(); + } + } + } + } + + saveCache(); + } + + /** + * Expand the plugin jars if needed + * @throws MavenException + */ + private void expandPluginJars() throws MavenException + { + File[] files = getPluginsDir().listFiles(); + + // First we expand any JARs that contain plugins to the unpack directory. + // This will be a different directory than the plugin jars + // MAVEN_HOME_LOCAL / maven.home.local was set to a different directory + // than MAVEN_HOME / maven.home. + for ( int i = 0; i < files.length; ++i ) + { + // Only unpack the JAR if it hasn't been already, or is newer + // than the plugin directory + if ( files[i].getName().endsWith( ".jar" ) ) + { + String directory = files[i].getName(); + directory = directory.substring( 0, directory.indexOf( ".jar" ) ); + File unzipDir = new File( getUnpackedPluginsDir(), directory ); + + // if there's no directory, or the jar is newer, expand the jar + if ( unzipDir.exists() == false + || files[i].lastModified() > unzipDir.lastModified() ) + { + if( log.isDebugEnabled() ) + { + log.debug( "Unpacking '" + directory + + "' plugin to directory --> " + + unzipDir.getAbsolutePath() ); + } + invalidateCache( directory ); + + File processed = getPluginProcessedMarker( unzipDir.getName() ); + if ( processed.exists() ) + { + processed.delete(); + } + + try + { + Expand unzipper = new Expand(); + unzipper.setSrc( files[i] ); + unzipper.setDest( unzipDir ); + unzipper.execute(); + } + catch (IOException e) + { + throw new MavenException("Unable to extract plugin: " + files[i], e); + } + } + } + } + } + + + /** * Attain the goals. * * @throws org.apache.maven.UnknownGoalException If one of the specified * goals refers to an non-existent goal. * @throws Exception If an exception occurs while running a goal. */ - public void attainGoals( Project project, List goals ) + public void attainGoals( Project project ) throws GoalException, Exception { - MavenJellyContext baseContext = createBaseContext( project ); - // Before attempting to attain the goals verify the project // if desired. - // 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(); + project.processDependencies(); - // Set up the ant project. - buildAntProject( project, baseContext ); - - Session session = getJellySession(baseContext); - // add the global session to the pluginContext so that it can be used by tags - baseContext.setVariable( GLOBAL_SESSION_KEY, session ); - - WerkzProject werkzProject = buildWerkzProject( baseContext ); - - // ------------------------------------------------------------------------------------------------------------- - // Execution of the Jelly scripts: - // - // We run the Jelly scripts in the following order: - // - // 1) driver.jelly - doesn't exist anymore - // 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 = PluginManager.class.getClassLoader().getResourceAsStream( "driver.jelly" ); - //JellyScriptHousing driverHousing = createJellyScriptHousing( project, null, driver ); - //driverHousing.setSource( "driver.jelly" ); - //mapper.parse( new InputStreamReader( driver ), driverHousing ); - //runJellyScriptHousing( driverHousing, baseContext ); - - // FIXME: Part of this belongs as a method on Project, e.g. the name and - // construction of maven.xml - // Project's Jelly script - if ( project.hasMavenXml() ) - { - File mavenXml = project.getMavenXml(); - //!!! This is the cause of the classloader problems!! - JellyScriptHousing jellyScriptHousing = createJellyScriptHousing( project, null, mavenXml ); - mapper.parse( new FileReader( mavenXml ), jellyScriptHousing ); - runJellyScriptHousing( jellyScriptHousing, baseContext ); - } - - // Parent's Jelly script - // FIXME: What about further up the chain? - if ( project.hasParent() && project.getParent().hasMavenXml() ) + // If this project has a parent then we will load it's maven.xml + // file into this project so any goals the parent specifies in + // its maven.xml file are available to the child. We load this + // before the child's maven.xml so that the child can override + // any desired goals. + if ( project.hasParent() ) { - // FIXME: this is a badly named method - File f = project.parentMavenXml(); + project.loadJellyScript( project.parentMavenXml() ); + } - if ( f.exists() ) - { - JellyScriptHousing jellyScriptHousing = createJellyScriptHousing( project, null, f ); - mapper.parse( new FileReader( f ), jellyScriptHousing ); - runJellyScriptHousing( jellyScriptHousing, baseContext ); - } + // Currently we will not attempt to load a maven.xml file when the project.xml + // file is not present. + if ( project.getFile() != null ) + { + File mavenXmlFile = new File( project.getFile().getParentFile(), + MavenConstants.BUILD_FILE_NAME ); + loadJellyScript( mavenXmlFile, project ); } - // ---------------------------------------------------------------------- - // Default goal handling - // ---------------------------------------------------------------------- + // There will always be at least one goal present which is the build:start + // goal. If this is the only goal present then we want to add either the + // default goal specified in the project.xml file, or the default goal + // specified in the driver.properties file. - if ( goals.size() == 0 ) + //if ( goalNames.size() == 1 ) + if ( project.getGoalNames().size() == 0) { - String defaultGoalName = mapper.getDefaultGoalName(); - - if ( defaultGoalName != null ) + if (project.getContext().getWerkzProject() == null) { - goals.add( defaultGoalName ); + throw new NoGoalException("No goal specified and no project.xml found."); + } + else + { + String defaultGoalName = project.getContext().getWerkzProject().getDefaultGoalName(); + + if ( defaultGoalName != null ) + { + project.getGoalNames().add( defaultGoalName ); + } } } - // Plugin Jelly scripts - for ( Iterator i = goals.iterator(); i.hasNext(); ) + //project.getGoalNames().add( BUILD_END_GOAL ); + + for ( Iterator i = project.getGoalNames().iterator(); i.hasNext(); ) { String goalName = (String) i.next(); + prepForGoal( goalName, project ); + Goal eachGoal = project.getContext().getWerkzProject().getGoal( goalName ); - // Now at this point we should be able to 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. - - Set plugins = mapper.resolveJellyScriptHousings( goalName ); - - for ( Iterator j = plugins.iterator(); j.hasNext(); ) + if ( eachGoal == null ) { - JellyScriptHousing jellyScriptHousing = (JellyScriptHousing) j.next(); + throw new UnknownGoalException( goalName ); + } + } + Session session = new JellySession( project.getContext().getXMLOutput() ); + Thread.currentThread().setContextClassLoader( null ); - //!!! Not sure why the housing is null. - if ( jellyScriptHousing != null ) - { - runJellyScriptHousing( jellyScriptHousing, baseContext ); - } - } + // add the global session to the context so that it can be used by tags + project.getContext().setVariable(GLOBAL_SESSION_KEY, session); - Goal goal = baseContext.getWerkzProject().getGoal( goalName ); - goal.attain( session ); + for ( Iterator i = project.getGoalNames().iterator(); i.hasNext(); ) + { + String eachGoalName = (String) i.next(); + Goal eachGoal = project.getContext().getWerkzProject().getGoal( eachGoalName ); + eachGoal.attain( session ); } } /** - * - * @param context the base context for the werkz project - * @return a configured werkz project - */ - private WerkzProject buildWerkzProject(MavenJellyContext context) - { - // We put in our listener to frob the session. - MavenAttainGoalListener listener = new MavenAttainGoalListener(); - listener.setBaseContext( context ); - listener.setPluginManager( this ); - WerkzProject werkzProject = new WerkzProject(); - werkzProject.addAttainGoalListener( listener ); - context.setWerkzProject( werkzProject ); - return werkzProject; - } - - /** - * Get the Werkz Session - * FIXME: Describe what it's for? - * @param baseContext the maven context the session should use - * @return A Werkz Session - * @throws Exception FIXME For anything + * Load a user jelly script. This may be a maven.xml file for a project or + * a shared jelly script used by the reactor. + * + * @param jellyScript The jelly. + * + * @throws Exception If an error occurs while attempting to load the file. */ - private Session getJellySession(MavenJellyContext baseContext) throws Exception - { - // Create the Jelly session - Session session = new JellySession( getJellyOutputSink() ); - session.setAttribute( XML_OUTPUT, getJellyOutputSink() ); - session.setAttribute( PLUGIN_MANAGER, this ); - session.setAttribute( BASE_CONTEXT, baseContext ); - return session; - } - - private void runJellyScriptHousing( JellyScriptHousing jellyScriptHousing, MavenJellyContext context ) + public void loadJellyScript( File jellyScript, Project project ) throws Exception { - //!!! I'm not sure how a plugin housing is coming out as null but some - // are. I need to add more tests and clean up the pre-parser. + if ( jellyScript.exists() == false ) + { + return; + } - if ( jellyScriptHousing != null ) + Set originalGoals = new HashSet( project.getContext().getWerkzProject().getGoals() ); + + for ( Iterator i = originalGoals.iterator(); i.hasNext(); ) { - Script s = jellyScriptHousing.getScript(); + Goal eachGoal = (Goal) i.next(); - if ( s != null ) + if ( eachGoal.getAction() == null ) { - try - { - // DG HACK 1 - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader( jellyScriptHousing.getClassLoader() ); - // END DG HACK 1 - jellyScriptHousing.getScript().run( context, getJellyOutputSink() ); - // DG HACK 2 - Thread.currentThread().setContextClassLoader( loader ); - // END DG HACK 2 - } - catch ( Exception e ) - { - System.err.println( "Error running '" + jellyScriptHousing.getSource() + "'" ); - throw e; - } + i.remove(); } } - } - public MavenJellyContext createPluginContext( String goalName, MavenJellyContext parent ) - { - JellyScriptHousing jellyScriptHousing = mapper.getPluginHousing( goalName ); + transientCacheManager.setPluginScript( jellyScript ); + transientCacheManager.parse(); - return createPluginContext( jellyScriptHousing, parent ); + for ( Iterator i = transientCacheManager.getDynaTagLibDecls().iterator(); i.hasNext();) + { + prepDynamicTagLib( (String) i.next(), project ); + } + + // Now we have put the werkz project that belong to the plugin manager into the + // the context so any goals with the same name encountered in the maven.xml file + // should override goals in the plugin manager werkz project. + JellyUtils.runScript( jellyScript, + project.getFile().getParentFile().toURL(), + project.getContext(), + project.getContext().getXMLOutput() ); } - public MavenJellyContext createPluginContext( JellyScriptHousing jellyScriptHousing, MavenJellyContext parent ) + /** + * Perform any required initialization to enable a goal + * to be met. + * + * @param initialGoalToPrep The initial goal to prepare for. There may + * also be additional goals to prep for once prequisite goals + * are taken into consideration. + * + * @throws Exception If an error occurs while attempting to + * prepare for the goal. + */ + public void prepForGoal( String initialGoalToPrep, Project project ) + throws Exception { - // Now we have to modify the context here or create a new one - // so that we can take the following into consideration: - // - // ${plugin} - // ${plugin.dir} - // ${plugin.resources} - - Project project = jellyScriptHousing.getProject(); - File projectFile = project.getFile(); - File unpackedPluginDir = projectFile.getParentFile(); - - MavenJellyContext pluginContext = new MavenJellyContext( parent ); - pluginContext.setVariable( "plugin", jellyScriptHousing.getProject() ); - pluginContext.setVariable( "plugin.dir", unpackedPluginDir ); - pluginContext.setVariable( "plugin.resources", new File( unpackedPluginDir, "plugin-resources" ) ); + LinkedList goalsToPrep = new LinkedList(); + Set seen = new HashSet(); + String goalToPrep = null; - // Now I need to merge the properties of the project with the plugin properties so - // that plugin properties can be overriden by the project. + goalsToPrep.add( initialGoalToPrep ); - // The JellyScriptHousing contains the project for this plugin and therefore contains the - // properties. We want to make these available in the plugin as simple ${foo} references. - //pluginContext.getVariables().putAll( jellyScriptHousing.getProject().getProjectProperties() ); + while ( goalsToPrep.isEmpty() == false ) + { + goalToPrep = (String) goalsToPrep.removeFirst(); - //System.out.println( "jellyScriptHousing = " + jellyScriptHousing.getProject().getName() ); - //System.out.println( "jellyScriptHousing = " + jellyScriptHousing.getProject().getProjectProperty( "maven.compile.target") ); + if ( seen.contains( goalToPrep ) ) + { + continue; + } - return pluginContext; - } + seen.add( goalToPrep ); - private MavenJellyContext createBaseContext( Project project ) - { - // We are making the compileScriptContext the parent so that the werkz - // project is present - MavenJellyContext baseContext = new MavenJellyContext( compileScriptContext ); - baseContext.setInherit( true ); + // We check to see if the goalToPrep has been defined in any user + // jelly script. This allows any goal specified in a plugin to be + // overriden if a user desires. + String spec = transientCacheManager.getPluginCache().getProperty( goalToPrep ); - // Set the created context, and put the project itself in the context. This - // is how we get the ${pom} reference in the project.xml file to work. - baseContext.setProject( project ); + if ( spec == null ) + { + spec = cacheManager.getPluginCache().getProperty( goalToPrep ); - // Now we have to setup the classloaders correctly + if ( spec != null ) + { + prepForCallbacks( goalToPrep, project ); + loadPlugin( spec, project ); + } + } - return baseContext; + // Find any prerequisite goals and add them to the list of + // goals to prepare. + List prereqs = getPrereqs( goalToPrep ); + goalsToPrep.addAll( prereqs ); + } } - /** XML output goes to System.out*/ - private XMLOutput output; + // ---------------------------------------------------------------------- + // P R E P A R A T I O N M E T H O D S + // ---------------------------------------------------------------------- - private XMLOutput getJellyOutputSink() + /** + * Prepare and load plugins based upon callback dependencies. + * + * @param goalName The goal name. + * + * @throws Exception If an error occurs while attempting to preprare + * callback dependencies. + */ + void prepForCallbacks( String goalName, Project project ) throws Exception { - if ( output == null ) + if ( cacheManager.getCallbackCache().containsKey( goalName + ".pre" ) ) { - PrintStream consoleOut = System.out; - //PrintStream consoleErr = System.err; - - Writer writer = new OutputStreamWriter( consoleOut ); - output = XMLOutput.createXMLOutput( writer, false ); + loadPlugins( cacheManager.getCallbackCache().getProperty( goalName + ".pre" ), project ); } - return output; + if ( cacheManager.getCallbackCache().containsKey( goalName + ".post" ) ) + { + loadPlugins( cacheManager.getCallbackCache().getProperty( goalName + ".post" ), project ); + } } /** - * FIXME: Not sure why building an Ant project is the plugin manager's responisbility - * - * @param project a maven project - * @param context a maven context - * @return an Ant project - * @throws Exception When any error occurs. FIXME this is bad. + * Prepare the tag libary for use. + * + * @param pluginName URI of the tag library to prepare. + * @param project Project to load the dyna tag libs into. + * + * @throws Exception if an error occurs while preparing dyna tag libs. */ - private GrantProject buildAntProject( Project project, MavenJellyContext context ) + void prepDynaTagLibs( String pluginName, Project project ) throws Exception { - // Create the build listener. - JellyBuildListener buildListener = new JellyBuildListener( getJellyOutputSink() ); - if ( System.getProperty( MavenConstants.DEBUG_ON ).equals( "true" ) ) - { - buildListener.setDebug( true ); - } + String depPlugins = cacheManager.getPluginDynaTagDepsCache().getProperty( pluginName ); - if ( System.getProperty( MavenConstants.EMACS_MODE_ON ).equals( "true" ) ) + if ( depPlugins == null ) { - buildListener.setEmacsMode( true ); + return; } - // Create our ant project. - GrantProject antProject = new GrantProject(); - antProject.setPropsHandler( new JellyPropsHandler( context ) ); - antProject.init(); - antProject.setBaseDir( project.getFile().getParentFile() ); - antProject.addBuildListener( buildListener ); - - context.setAntProject( antProject ); - AntTagLibrary.setProject( context, antProject ); + String[] list = StringUtils.split( depPlugins, "," ); - Path p = new Path( antProject ); - p.setPath( project.getDependencyClasspath() ); - antProject.addReference( MavenConstants.DEPENDENCY_CLASSPATH, p ); - - return antProject; + for ( int i = 0; i < list.length; i++ ) + { + prepDynamicTagLib( list[i], project ); + } } - - private JellyScriptHousing createJellyScriptHousing( Project project, File classesDirectory, File jelly ) + /** + * Prepare an individual dyna tag lib for use. + * + * @param uri URI of the dyna tag. + * @param project Project to load the dyna tag lib into. + * + * @throws Exception If an error occurs preparing the dynamic tag library. + */ + void prepDynamicTagLib( String uri, Project project ) throws Exception { - InputStream is = null; + String depPlugin = cacheManager.getDynaTagLibCache().getProperty( uri ); - if ( jelly.exists() ) + // Some of the may be currently null because some dyna tag libs aren't + // in plugins but in the driver.jelly file. This needs to be unified where + // everything comes from a plugin. + if ( depPlugin == null ) { - is = new FileInputStream( jelly ); + return; } - JellyScriptHousing jellyScriptHousing = createJellyScriptHousing( project, classesDirectory, is ); - jellyScriptHousing.setSource( jelly.getPath() ); - - return jellyScriptHousing; + loadPlugin( depPlugin, project ); } /** - * @param plugin the plugin to create a housing for - * @return a housing - * @throws MalformedURLException - * @throws Exception + * Retrieve the prerequisites for a goal. + * + * @param name The goal name. + * + * @return A list of <code>String</code> prerequisite goal names. */ - private JellyScriptHousing createJellyScriptHousing(JellyPlugin plugin) - throws MalformedURLException, Exception + List getPrereqs( String name ) { - JellyScriptHousing jellyScriptHousing = new JellyScriptHousing(); - jellyScriptHousing.setId( plugin.getId() ); + String spec = transientCacheManager.getGoalCache().getProperty( name ); + + if ( spec == null ) + { + spec = cacheManager.getGoalCache().getProperty( name ); + } + + if ( spec == null ) + { + return Collections.EMPTY_LIST; + } + + int splitLoc = spec.indexOf( ">" ); - // Now before we even _compile_ the jelly script you must set the classloader because - // the whole process needs to be able to resolve the tags. I thought a StaticTag was - // supposed to be created and the search deferred but this is _not_ the case. - jellyScriptHousing.setClassLoader( plugin.getClassLoader() ); - jellyScriptHousing.setProject( plugin.getProject() ); - jellyScriptHousing.setScript( plugin.getCompiledScriptFor(compileScriptContext) ); + if ( ( splitLoc < 0 ) + || + ( splitLoc + 1 ) == spec.length() ) + { + return Collections.EMPTY_LIST; + } + + String prereqSpec = spec.substring( splitLoc + 1 ); - return jellyScriptHousing; + StringTokenizer tokens = new StringTokenizer( prereqSpec, "," ); + + List prereqs = new ArrayList(); + + while ( tokens.hasMoreTokens() ) + { + prereqs.add( tokens.nextToken() ); + } + + return prereqs; } - private JellyScriptHousing createJellyScriptHousing( Project project, File unpackedPluginDirectory, InputStream jelly ) + // ---------------------------------------------------------------------- + // P L U G I N L O A D I N G + // ---------------------------------------------------------------------- + + /** + * Load the specified plugin. + * + * @param name The name of the plugin to load. + * + * @throws Exception If an error occurs while initializing the plugin. + */ + public void loadPlugin( String name, Project project ) throws Exception { - JellyScriptHousing jellyScriptHousing = new JellyScriptHousing(); + if ( isLoaded( project, name ) ) + { + return; + } - // Now we are going to create a ClassLoader for the plugins classes and the JARs - // that it depends on for executing. - ForeheadClassLoader classLoader = new ForeheadClassLoader( mavenRootClassLoader, project.getId() ); + File pluginScript = getPluginScript( name ); - if ( unpackedPluginDirectory != null ) + if ( pluginScript.exists() == false ) { - // We will add the plugin classes to the plugin class loader. - classLoader.addURL( unpackedPluginDirectory.toURL() ); + return; } - // We will add the JARs that have been instructed to be place in the class loader - // for use in the plugin in the plugin class loader. - processDependencies( project, classLoader ); + // We need to make sure all the dyna tag libs are initialized for this + // plugin. + prepDynaTagLibs( name, project ); - //displayClassLoaderContents( project, classLoader ); + File unpackedPluginDir = getUnpackedPluginDir( name ); - // Now before we even _compile_ the jelly script you must set the classloader because - // the whole process needs to be able to resolve the tags. I thought a StaticTag was - // supposed to be created and the search deferred but this is _not_ the case. - compileScriptContext.setClassLoader( classLoader ); + // Use classworlds + Forehead.getInstance().getClassLoader( "root.maven" ) + .addURL( unpackedPluginDir.toURL() ); - // Now lets compile the script once so we can use it repeatedly. - Script script = null; + Project pluginProject = + MavenUtils.getProject( new File( unpackedPluginDir, + "project.xml" ), + project.getContext(), + false ); - // Projects may not have a maven.xml file. - if ( jelly != null ) + if ( isPluginProcessed( name ) == false ) { - try - { - script = JellyUtils.compileScript( jelly, compileScriptContext ); - } - catch ( Exception e ) - { - e.printStackTrace(); - } + pluginProject.verifyDependencies(); + + // Mark the plugin as processed. + FileUtils.fileWrite( getPluginProcessedMarker( name ).getPath(), + "plugin has been processed."); } - jellyScriptHousing.setClassLoader( classLoader ); - jellyScriptHousing.setProject( project ); - jellyScriptHousing.setScript( script ); - jellyScriptHousing.setId( project.getId() ); + // place dependencies on the right classloaders + pluginProject.processDependencies(); + + // We need to create a separate context for the plugin.jelly script to + // run against because we need our values of "plugin" and "plugin.dir" + // to have distinct values. Everything else can be taken from the + // project's context. We also want project properties to override any + // default plugin properties. When we make the pluginContext, context + // inheritance is on so when we integrate the plugin properties if the + // project has already defined an overriding value it won't get + // clobbered. The plugin defaults will only be used if the + // project hasn't provided a value. + MavenJellyContext pluginContext = + new MavenJellyContext( project.getContext() ); + MavenUtils.integrateMapInContext( + getPluginProperties( unpackedPluginDir ), + pluginContext ); + pluginContext.setVariable( "plugin", pluginProject ); + pluginContext.setVariable( "plugin.dir", unpackedPluginDir ); - return jellyScriptHousing; - } + // Set a context variable that points to the plugin resources directory + pluginContext.setVariable( "plugin.resources", + new File( unpackedPluginDir, "plugin-resources" ) ); - private void displayClassLoaderContents( Project project, ForeheadClassLoader classLoader ) - { - URL[] urls = classLoader.getURLs(); + project.addPluginContext( pluginProject.getId(), pluginContext ); - System.out.println( "project.getName() = " + project.getName() ); + JellyUtils.runScript( pluginScript, + unpackedPluginDir.toURL(), + pluginContext, + pluginContext.getXMLOutput() ); - for ( int i = 0; i < urls.length; i++ ) + // The project werkz project seems to come up null when there is no maven.xml + // that is processed first. An inheritance bug in the context it seems. + if ( project.getContext().getWerkzProject() == null ) { - System.out.println( "urls[" + i + "] = " + urls[i] ); + project.getContext().setWerkzProject( pluginContext.getWerkzProject() ); } - - ClassLoader parent = classLoader.getParent(); - if (parent != null && parent instanceof ForeheadClassLoader) + + // The plugin has now been loaded for use for a particular project + // So add it to the list. + loadedPlugins.add( project.hashCode() + name ); + } + + /** + * Load plugins specified in a whitespace delimited string. + * + * @param names The whitespace delimited string of plugin names. + * + * @throws Exception If an error occurs while attempting to load + * the plugins. + */ + void loadPlugins( String names, Project project ) + throws Exception + { + StringTokenizer tokens = new StringTokenizer( names, "," ); + + while ( tokens.hasMoreTokens() ) { - System.out.println("Displaying Parent classloader: "); - displayClassLoaderContents(project, (ForeheadClassLoader)classLoader.getParent()); + loadPlugin( tokens.nextToken(), project ); } } + // ---------------------------------------------------------------------- + // C A C H I N G + // ---------------------------------------------------------------------- + /** - * process the dependencies for this project + * Cache a plugin. + * + * @param name The plugin name. + * + * @throws Exception If an error occurs while attempting to analyze + * and cache plugin information. */ - public static void processDependencies( Project project, ForeheadClassLoader classLoader ) - throws MalformedURLException + void cachePlugin( String name ) + throws Exception { - if ( project.getArtifacts() == null ) + log.debug( "Processing Plugin: " + name ); + + File pluginScript = getPluginScript( name ); + + // There are some plugins that don't have plugin.jelly scripts like + // the examples plugin. This certainly isn't the norm but we want + // avoid useless error messages filling up the console. + if ( pluginScript.exists() ) { - return; + cacheManager.setPluginScript( pluginScript ); + cacheManager.parse(); } + } - for ( Iterator i = project.getArtifacts().iterator(); i.hasNext(); ) + /** + * Save cache information to disk. + * + * @throws Exception If an error occurs while saving the cache. + */ + void saveCache() + throws Exception + { + cacheManager.saveCache(); + } + + /** + * Invalidate cache information for a single plugin. + * + * @param pluginName The name of the plugin to invalid cache entries. + */ + void invalidateCache( String pluginName ) + { + for ( Iterator i = cacheManager.getGoalCache().keySet().iterator(); i.hasNext(); ) { - Artifact artifact = (Artifact) i.next(); - Dependency dependency = artifact.getDependency(); - String classloaderProperty = dependency.getProperty( "classloader" ); - - // Only add compile type dependencies to classloader - // what about ejbs etc - if ( dependency.getType().equals( "jar" ) ) + String eachGoal = (String) i.next(); + + if ( cacheManager.getPluginCache().getProperty( eachGoal ).equals( pluginName ) ) { - // We have the jar and the classloader to push it into so - // lets do it! - if ( artifact.exists() ) - { - if ( classloaderProperty != null ) - { - ForeheadClassLoader otherClassLoader = - Forehead.getInstance().getClassLoader(classloaderProperty); - if (otherClassLoader != null) - { - otherClassLoader.addURL( artifact.getFile().toURL() ); - } - } - else - { - classLoader.addURL( artifact.getFile().toURL() ); - } - } + i.remove(); + cacheManager.getPluginCache().remove( eachGoal ); + cacheManager.getCallbackCache().remove( eachGoal + ".pre" ); + cacheManager.getCallbackCache().remove( eachGoal + ".post" ); } } + } + /** + * Determine if a plugin has been cached. + * + * @param pluginName The name of the plugin to test. + * + * @return <code>true</code> if the plugin has already been cached, + * otherwise <code>false</code>. + */ + boolean isCached( String pluginName ) + { + return cacheManager.getPluginCache().contains( pluginName ); } /** - * Load plugins specified in a whitespace delimited string. + * Determine if a goal has been cached. * - * @param names The whitespace delimited string of plugin names. + * @param name The name of the goal to test. * - * @throws Exception If an error occurs while attempting to load - * the plugins. + * @return <code>true</code> if the goal has already been cached, + * otherwise <code>false</code>. */ - void loadPlugins( String names, Project project ) - throws Exception + boolean isGoalCached( String name ) { - StringTokenizer tokens = new StringTokenizer( names, "," ); + return cacheManager.getGoalCache().containsKey( name ); + } - while ( tokens.hasMoreTokens() ) - { - loadPlugin( tokens.nextToken() ); - } + Set getTagLibsCache() + { + return cacheManager.getDynaTagLibCache().keySet(); } /** @@ -828,7 +891,7 @@ File getPluginProcessedMarker( String pluginName ) { return new File( new File( getUnpackedPluginsDir(), pluginName ), - ".processed" ); + ".processed" ); } /** @@ -851,11 +914,44 @@ * @return <code>true</code> if the plugin has been loaded, * otherwise <code>false</code>. */ - boolean isLoaded( String id ) + boolean isLoaded( Project project, String name ) + { + return loadedPlugins.contains( project.hashCode() + name ); + } + + /** + * Retrieve the plugin's default properties. + * + * @param unpackedPluginDir The location of the unpacked plugin. + * + * @return The default properties file for the plugin, or <code>null</code> + * if no such plugin. + * + * @throws IOException If an IO error occurs while attempting to read the + * plugin's properties. + */ + Properties getPluginProperties( File unpackedPluginDir ) + throws IOException { - return loadedPlugins.get( id ) != null; + File propsFile = new File( unpackedPluginDir, PLUGIN_PROPERTIES_NAME ); + + if ( propsFile.exists() == false ) + { + return null; + } + + Properties props = new Properties(); + + FileInputStream in = new FileInputStream( propsFile ); + + props.load( in ); + + in.close(); + + return props; } + /** * Retrieve the directory where the specified plugin is unpacked. * @@ -869,6 +965,20 @@ } /** + * Retrieve the plugin's entry-point jelly script. + * + * @param pluginName The name of the plugin. + * + * @return The entry-point script for the plugin, or <code>null</code> if + * no such plugin. + */ + File getPluginScript( String pluginName ) + { + return new File( getUnpackedPluginDir( pluginName ), + PLUGIN_SCRIPT_NAME ); + } + + /** * Sets the pluginsDir attribute of the PluginManager object * * @param pluginsDir The maven plugin directory. @@ -896,6 +1006,7 @@ void setUnpackedPluginsDir( File unpackedPluginsDir ) { this.unpackedPluginsDir = unpackedPluginsDir; + cacheManager.setUnpackedPluginsDir( unpackedPluginsDir ); } /** @@ -906,5 +1017,31 @@ File getUnpackedPluginsDir() { return unpackedPluginsDir; + } + + /** + * Load and install a plugin + * @todo should check if it's already installed. + * @todo I'm not sure if caching needs to be called. + * @param file + */ + public void installPlugin(Project project, File file) throws Exception + { + // copy the file to the unpacked plugins dir + FileUtils.copyFileToDirectory(file, getPluginsDir()); + String pluginName = file.getCanonicalFile().getName(); + pluginName = pluginName.substring(0, pluginName.indexOf(".jar")); + String newFileName = getPluginsDir().getCanonicalPath() + + File.separator + file.getCanonicalFile().getName(); + // expand it + Expand unzipper = new Expand(); + unzipper.setSrc( new File(newFileName)); + File unzipDir = new File( getUnpackedPluginsDir(), pluginName); + unzipper.setDest( unzipDir ); + unzipper.execute(); + // load it + loadPlugin(pluginName, project); + cachePlugin(pluginName); + // FIXME: Does it need caching too? } } 1.14 +2 -2 maven/src/java/org/apache/maven/plugin/PluginCacheManager.java
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]