1 package org.apache.maven.plugin;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.net.MalformedURLException;
25 import java.net.URL;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35
36 import org.apache.commons.io.FileUtils;
37 import org.apache.commons.jelly.Script;
38 import org.apache.commons.jelly.expression.Expression;
39 import org.apache.commons.logging.Log;
40 import org.apache.commons.logging.LogFactory;
41 import org.apache.maven.AbstractMavenComponent;
42 import org.apache.maven.AntProjectBuilder;
43 import org.apache.maven.MavenConstants;
44 import org.apache.maven.MavenException;
45 import org.apache.maven.MavenSession;
46 import org.apache.maven.MavenUtils;
47 import org.apache.maven.UnknownGoalException;
48 import org.apache.maven.jelly.JellyUtils;
49 import org.apache.maven.jelly.MavenJellyContext;
50 import org.apache.maven.project.Dependency;
51 import org.apache.maven.project.Project;
52 import org.apache.maven.repository.Artifact;
53 import org.apache.maven.util.Expand;
54 import org.apache.maven.werkz.Goal;
55 import org.apache.maven.werkz.NoSuchGoalException;
56 import org.apache.maven.werkz.Session;
57 import org.apache.maven.werkz.WerkzProject;
58 import org.apache.maven.werkz.jelly.JellySession;
59
60 import com.werken.forehead.Forehead;
61 import com.werken.forehead.ForeheadClassLoader;
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89 /**
90 * Plugin manager for MavenSession.
91 * <p/>
92 * The <code>PluginManager</code> deals with all aspects of a plugins lifecycle.
93 * <p/>
94 * This is <b>not</b> thread safe.
95 *
96 * @author <a href="mailto:jason@zenplex.com">Jason van Zyl</a>
97 * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
98 * @author <a href="mailto:brett@apache.org">Brett Porter</a>
99 * @version $Id: PluginManager.java 517014 2007-03-11 21:15:50Z ltheussl $
100 */
101 public class PluginManager
102 extends AbstractMavenComponent
103 {
104 /**
105 * Logger
106 */
107 private static final Log LOGGER = LogFactory.getLog( PluginManager.class );
108
109 /**
110 * The variable key that holds an implementation of the
111 * <code>com.werken.werkz.Session</code> in the parent scope context.
112 */
113 public static final String GLOBAL_SESSION_KEY = "maven.session.global";
114
115 public static final String PLUGIN_MANAGER = "maven.plugin.manager";
116
117 public static final String PLUGIN_HOUSING = "maven.plugin.script.housing";
118
119 public static final String GOAL_MAPPER = "maven.plugin.mapper";
120
121 /** */
122 public static final String BASE_CONTEXT = "maven.goalAttainmentContext";
123
124 /**
125 * The directory where plugin jars reside under Maven's home.
126 */
127 private File pluginsDir;
128
129 /**
130 * The directory where the plugin jars are unpacked to.
131 */
132 private File unpackedPluginsDir;
133
134 /**
135 * The directory where the user's plugin jars are installed.
136 */
137 private File userPluginsDir;
138
139 /**
140 * This contains a map of plugins, keyed by id.
141 */
142 private final Map pluginHousings = new HashMap();
143
144 /**
145 * This contains a map of plugins, keyed by artifact id.
146 */
147 private final Map artifactIdToHousingMap = new HashMap();
148
149 /**
150 * Maven session reference.
151 */
152 private MavenSession mavenSession;
153
154 /**
155 * Plugin cache manager.
156 */
157 private final PluginCacheManager cacheManager = new PluginCacheManager();
158
159 /**
160 * Goal to Plugins mapper.
161 */
162 private GoalToJellyScriptHousingMapper mapper = new GoalToJellyScriptHousingMapper();
163
164 /**
165 * Current plugins mapper (transient - includes maven.xml, etc). *
166 */
167 private GoalToJellyScriptHousingMapper transientMapper = mapper;
168
169 /**
170 * Plugins to be popped afterwards.
171 */
172 private Set delayedPops = new HashSet();
173
174 /**
175 * The current goal attainment base context.
176 */
177 private MavenJellyContext baseContext;
178
179 private static final String PLUGIN_TEMP_MAP = "PluginManager.PLUGIN_TEMP_MAP";
180
181 /**
182 * Default constructor.
183 *
184 * @param session The MavenSession this plugin manager will use
185 * until Maven shuts down.
186 */
187 public PluginManager( MavenSession session )
188 {
189 mavenSession = session;
190 }
191
192 /**
193 * Get the list of plugin files.
194 */
195 private Map getPluginFiles( File directory, boolean acceptDirectories )
196 {
197 File[] files = directory.listFiles();
198 if ( files == null )
199 {
200 return Collections.EMPTY_MAP;
201 }
202
203 Map pluginFiles = new HashMap();
204 for ( int i = 0; i < files.length; i++ )
205 {
206 String plugin = files[i].getName();
207 if ( files[i].isDirectory() && acceptDirectories )
208 {
209 pluginFiles.put( plugin, files[i] );
210 }
211 else
212 {
213 int index = plugin.indexOf( ".jar" );
214 if ( index >= 0 )
215 {
216 String name = plugin.substring( 0, index );
217 pluginFiles.put( name, files[i] );
218 }
219 }
220 }
221 return pluginFiles;
222 }
223
224 /**
225 * Load plugins.
226 *
227 * @throws MavenException when the plugin jars can't be expanded
228 */
229 private void loadUncachedPlugins( Map pluginFiles )
230 throws IOException, MavenException
231 {
232 LOGGER.debug( "Now loading uncached plugins" );
233
234 for ( Iterator i = pluginFiles.keySet().iterator(); i.hasNext(); )
235 {
236 String name = (String) i.next();
237 File pluginDir = (File) pluginFiles.get( name );
238
239 if ( !isLoaded( name ) )
240 {
241 JellyScriptHousing housing = createPluginHousing( pluginDir );
242 if ( housing != null )
243 {
244 cacheManager.registerPlugin( name, housing );
245 housing.parse( cacheManager );
246 housing.parse( mapper );
247 }
248 }
249 }
250 }
251
252 /**
253 * Initialize all plugins.
254 */
255 public void initialize()
256 throws IOException, MavenException
257 {
258 if ( LOGGER.isDebugEnabled() )
259 {
260 LOGGER.debug( "Initializing Plugins!" );
261 }
262
263 setPluginsDir( new File( mavenSession.getRootContext().getPluginsDir() ) );
264 setUnpackedPluginsDir( new File( mavenSession.getRootContext().getUnpackedPluginsDir() ) );
265 setUserPluginsDir( new File( mavenSession.getRootContext().getUserPluginsDir() ) );
266
267 if ( !getPluginsDir().isDirectory()
268 || ( ( getPluginsDir().listFiles() != null ) && ( getPluginsDir().listFiles().length == 0 ) ) )
269 {
270 throw new MavenException( "Maven was badly installed. Please reinstall it." );
271 }
272
273 if ( LOGGER.isDebugEnabled() )
274 {
275 LOGGER.debug( "Set plugin source directory to " + getPluginsDir().getAbsolutePath() );
276 LOGGER.debug( "Set unpacked plugin directory to " + getUnpackedPluginsDir().getAbsolutePath() );
277 LOGGER.debug( "Set user plugin directory to " + getUserPluginsDir().getAbsolutePath() );
278 }
279
280 verifyUnpackedPluginsDir();
281
282
283
284
285
286 Map pluginFiles = getPluginFiles( pluginsDir, true );
287 Map userPluginFiles = getPluginFiles( userPluginsDir, false );
288
289 if ( !userPluginFiles.isEmpty() )
290 {
291 pluginFiles.putAll( userPluginFiles );
292 }
293
294 cacheManager.loadCache( unpackedPluginsDir );
295
296 Map pluginDirs = expandPluginFiles( pluginFiles );
297
298 LOGGER.debug( "Now mapping cached plugins" );
299 if ( !cacheManager.mapPlugins( mapper, this, pluginDirs ) )
300 {
301 LOGGER.info( "Cache invalidated due to out of date plugins" );
302
303 for ( Iterator i = pluginHousings.values().iterator(); i.hasNext(); )
304 {
305 JellyScriptHousing housing = (JellyScriptHousing) i.next();
306 cacheManager.registerPlugin( housing.getName(), housing );
307 housing.parse( cacheManager );
308 housing.parse( mapper );
309 }
310 }
311
312 loadUncachedPlugins( pluginDirs );
313
314 cacheManager.saveCache( unpackedPluginsDir );
315
316 LOGGER.debug( "Finished initializing Plugins!" );
317 }
318
319 private Map expandPluginFiles( Map pluginFiles )
320 throws MavenException
321 {
322 Map pluginDirs = new HashMap();
323 for ( Iterator i = pluginFiles.keySet().iterator(); i.hasNext(); )
324 {
325 String name = (String) i.next();
326 File jarFile = (File) pluginFiles.get( name );
327 File dir = jarFile;
328 if ( !dir.isDirectory() )
329 {
330 dir = unpackPlugin( name, jarFile, true );
331 }
332 pluginDirs.put( name, dir );
333 }
334 return pluginDirs;
335 }
336
337 JellyScriptHousing loadPluginHousing( String name, File pluginDir )
338 throws IOException
339 {
340 JellyScriptHousing housing = (JellyScriptHousing) pluginHousings.get( name );
341 return ( housing == null ? createLazyPluginHousing( pluginDir ) : housing );
342 }
343
344 private JellyScriptHousing createPluginHousing( File pluginDir )
345 throws MavenException, IOException
346 {
347 JellyScriptHousing housing = createLazyPluginHousing( pluginDir );
348 if ( housing != null )
349 {
350 String artifactId = housing.getProject().getArtifactId();
351 mapArtifactIdToPluginHousing( artifactId, housing );
352 }
353 return housing;
354 }
355
356 void mapArtifactIdToPluginHousing( String artifactId, JellyScriptHousing housing )
357 {
358 if ( artifactIdToHousingMap.containsKey( artifactId ) )
359 {
360 JellyScriptHousing h = (JellyScriptHousing) artifactIdToHousingMap.get( artifactId );
361 LOGGER.warn( "WARNING: Plugin '" + artifactId + "' is already loaded from " + h.getName()
362 + "; attempting to load " + housing.getName() );
363 }
364 artifactIdToHousingMap.put( artifactId, housing );
365 }
366
367 private JellyScriptHousing createLazyPluginHousing( File pluginDir )
368 throws IOException
369 {
370 if ( !pluginDir.isDirectory() || !new File( pluginDir, "project.xml" ).exists() )
371 {
372 LOGGER.debug( "Not a plugin directory: " + pluginDir );
373 return null;
374 }
375
376 String pluginName = pluginDir.getName();
377
378 LOGGER.debug( "Loading plugin '" + pluginName + "'" );
379
380 JellyScriptHousing jellyScriptHousing = new JellyScriptHousing( pluginDir, mavenSession.getRootContext() );
381
382 pluginHousings.put( pluginName, jellyScriptHousing );
383
384 return jellyScriptHousing;
385 }
386
387 private boolean isLoaded( String name )
388 {
389 return pluginHousings.containsKey( name );
390 }
391
392 /**
393 * @param project
394 * @param jelly
395 * @param systemId the system identifier to help resolve relative URLs
396 * @return JellyScriptHousing
397 * @throws Exception
398 * @deprecated get rid of this - it duplicates functionality in the housing
399 * @todo refactor into housing
400 * @todo don't throw Exception
401 * @todo get rid of this - it duplicates functionality in the housing
402 */
403 private JellyScriptHousing createJellyScriptHousing( Project project, String systemId, InputStream jelly )
404 throws Exception
405 {
406 JellyScriptHousing jellyScriptHousing = new JellyScriptHousing();
407
408 try
409 {
410
411 Script script = JellyUtils.compileScript( jelly, systemId, project.getContext() );
412
413 jellyScriptHousing.setProject( project );
414 jellyScriptHousing.setScript( script );
415 }
416 catch ( Exception e )
417 {
418
419 throw new MavenException( "Error parsing driver.jelly", e );
420 }
421
422 return jellyScriptHousing;
423 }
424
425 /**
426 * @param project
427 * @param jelly
428 * @return JellyScriptHousing
429 * @todo [1.0] into the housing?
430 */
431 private JellyScriptHousing createJellyScriptHousing( Project project, File jelly )
432 {
433 JellyScriptHousing jellyScriptHousing = new JellyScriptHousing();
434
435 jellyScriptHousing.setProject( project );
436 jellyScriptHousing.setSource( jelly );
437
438 return jellyScriptHousing;
439 }
440
441 /**
442 * Process the dependencies of the project, adding dependencies to the
443 * appropriate classloader etc
444 *
445 * @throws MalformedURLException if a file can't be converted to a URL.
446 * @throws Exception for any other issue. FIXME
447 */
448 public void processDependencies( Project project )
449 throws MalformedURLException, Exception
450 {
451 if ( ( project.getArtifacts() == null ) || project.getArtifacts().isEmpty() )
452 {
453 LOGGER.debug( "No dependencies to process for project " + project.getName() );
454 return;
455 }
456
457 ClassLoader cl = project.getContext().getClassLoader();
458 if ( !( cl instanceof ForeheadClassLoader ) )
459 {
460 return;
461 }
462
463 ForeheadClassLoader projectClassLoader = (ForeheadClassLoader) cl;
464 LOGGER.debug( "Processing dependencies for project " + project.getName() + "; classloader " + projectClassLoader );
465
466
467 for ( Iterator i = project.getArtifacts().iterator(); i.hasNext(); )
468 {
469 Artifact artifact = (Artifact) i.next();
470 Dependency dependency = artifact.getDependency();
471 if ( dependency.isPlugin() )
472 {
473
474 installPlugin( artifact.getFile(), project );
475 }
476
477
478 String dependencyClassLoader = dependency.getProperty( "classloader" );
479
480
481 if ( artifact.exists() )
482 {
483
484
485 if ( dependency.isAddedToClasspath() )
486 {
487 if ( dependencyClassLoader != null )
488 {
489 LOGGER.debug( "DEPRECATION: " + dependency.getId() + " in project " + project.getId()
490 + " forces the classloader '" + dependencyClassLoader + "'" );
491 LOGGER.debug( " This behaviour is deprecated. Please refer to the FAQ" );
492 ForeheadClassLoader loader = Forehead.getInstance().getClassLoader( dependencyClassLoader );
493 if ( loader == null )
494 {
495 LOGGER.warn( "classloader '" + dependencyClassLoader
496 + "' not found. Adding dependencies to the project classloader instead" );
497 loader = projectClassLoader;
498 }
499 else
500 {
501 LOGGER.debug( "poking dependency " + artifact.getFile() + " into classloader "
502 + dependencyClassLoader );
503 }
504 loader.addURL( artifact.getFile().toURL() );
505 }
506 else
507 {
508 LOGGER.debug( "adding dependency " + artifact.getFile() + " into project classloader" );
509 projectClassLoader.addURL( artifact.getFile().toURL() );
510 }
511 }
512 else
513 {
514 LOGGER.debug( "Non classpath dependency: '" + artifact.getFile() + "' not added to classpath" );
515 }
516 }
517 else
518 {
519 LOGGER.info( "Artifact '" + artifact.getFile() + "' not found to add to classpath" );
520 }
521 }
522
523
524 project.getContext().setClassLoader( projectClassLoader );
525 }
526
527 List readMavenXml( Project project, GoalToJellyScriptHousingMapper mapper )
528 throws MavenException
529 {
530 Project p = project;
531 List projectHousings = new ArrayList();
532
533
534 while ( p != null )
535 {
536 if ( p.hasMavenXml() )
537 {
538 File mavenXml = p.getMavenXml();
539
540 JellyScriptHousing jellyScriptHousing = createJellyScriptHousing( project, mavenXml );
541 jellyScriptHousing.parse( mapper );
542 projectHousings.add( jellyScriptHousing );
543 }
544 p = p.getParent();
545 }
546 return projectHousings;
547 }
548
549 MavenJellyContext setupBaseContext( Project project )
550 {
551 MavenJellyContext prevBaseContext = baseContext;
552 baseContext = new MavenJellyContext( mavenSession.getRootContext() );
553 baseContext.setInherit( true );
554 JellyUtils.populateVariables( baseContext, project.getContext() );
555 project.pushContext( baseContext );
556 baseContext.setProject( project );
557
558 return prevBaseContext;
559 }
560
561 /**
562 * Attain the goals.
563 *
564 * @throws Exception If one of the specified
565 * goals refers to an non-existent goal.
566 * @throws Exception If an exception occurs while running a goal.
567 * @todo stop throwing Exception
568 */
569 public void attainGoals( Project project, List goals )
570 throws Exception
571 {
572 MavenJellyContext prevBaseContext = setupBaseContext( project );
573
574
575 AntProjectBuilder.build( project, baseContext );
576
577
578 transientMapper = new GoalToJellyScriptHousingMapper();
579
580
581 Session session = new JellySession( baseContext.getXMLOutput() );
582 session.setAttribute( BASE_CONTEXT, baseContext );
583 session.setAttribute( PLUGIN_MANAGER, this );
584 session.setAttribute( GOAL_MAPPER, transientMapper );
585
586
587 baseContext.setVariable( GLOBAL_SESSION_KEY, session );
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606 InputStream driver = getClass().getResourceAsStream( "/driver.jelly" );
607 JellyScriptHousing driverHousing = createJellyScriptHousing( project, getClass().getResource( "/driver.jelly" )
608 .toString(), driver );
609
610 driver.close();
611 driver = getClass().getResourceAsStream( "/driver.jelly" );
612 driverHousing.parse( transientMapper, null, driver );
613 driver.close();
614
615 List projectHousings = readMavenXml( project, transientMapper );
616
617 if ( goals != null )
618 {
619 for ( Iterator i = goals.iterator(); i.hasNext(); )
620 {
621 String goal = (String) i.next();
622 if ( goal.trim().length() == 0 )
623 {
624 i.remove();
625 }
626 }
627 }
628
629
630 String defaultGoalName = transientMapper.getDefaultGoalName();
631
632 if ( ( project.getBuild() != null ) && ( project.getBuild().getDefaultGoal() != null ) )
633 {
634 defaultGoalName = project.getBuild().getDefaultGoal();
635 }
636
637 if ( defaultGoalName != null )
638 {
639
640 Expression e = JellyUtils.decomposeExpression( defaultGoalName, baseContext );
641 defaultGoalName = e.evaluateAsString( baseContext );
642 baseContext.setVariable( MavenConstants.DEFAULT_GOAL, defaultGoalName );
643
644 if ( ( goals != null ) && ( goals.size() == 0 ) )
645 {
646 LOGGER.debug( "Using default goal: " + defaultGoalName );
647 goals.add( defaultGoalName );
648 }
649 }
650 if ( goals == null )
651 {
652
653 goals = Collections.EMPTY_LIST;
654 }
655 else
656 {
657
658 goals.add( 0, "build:start" );
659 goals.add( "build:end" );
660 }
661
662 transientMapper.merge( mapper );
663
664 WerkzProject werkzProject = new WerkzProject();
665 baseContext.setWerkzProject( werkzProject );
666
667 Set pluginSet = new HashSet();
668
669 Set oldDelayedPops = new HashSet( delayedPops );
670 delayedPops.clear();
671
672 Thread.currentThread().setContextClassLoader( null );
673
674 try
675 {
676 runScript( driverHousing, baseContext );
677 transientMapper.addResolvedPlugins( Collections.singletonList( driverHousing ) );
678
679
680
681
682 project.verifyDependencies();
683 processDependencies( project );
684
685 for ( Iterator j = projectHousings.iterator(); j.hasNext(); )
686 {
687 JellyScriptHousing housing = (JellyScriptHousing) j.next();
688 runScript( housing, baseContext );
689 }
690 transientMapper.addResolvedPlugins( projectHousings );
691
692
693 for ( Iterator i = goals.iterator(); i.hasNext(); )
694 {
695 String goalName = (String) i.next();
696
697 pluginSet.addAll( prepAttainGoal( goalName, baseContext, transientMapper ) );
698 }
699
700
701 for ( Iterator i = goals.iterator(); i.hasNext(); )
702 {
703 String goalName = (String) i.next();
704 LOGGER.debug( "attaining goal " + goalName );
705 try
706 {
707 Goal goal = werkzProject.getGoal( goalName );
708 if ( ( goal == null ) || ( goal.getAction() == null ) )
709 {
710 throw new NoSuchGoalException( goalName );
711 }
712 goal.attain( session );
713 }
714 catch ( NoSuchGoalException e )
715 {
716 throw new UnknownGoalException( goalName );
717 }
718 }
719 }
720 finally
721 {
722 cleanupAttainGoal( pluginSet );
723 delayedPops = oldDelayedPops;
724 reinstallPlugins( project.getContext() );
725 project.popContext();
726 baseContext = prevBaseContext;
727 }
728 }
729
730 /**
731 * @todo don't throw Exception
732 */
733 public void cleanupAttainGoal( Set pluginSet )
734 throws Exception
735 {
736 delayedPops.addAll( pluginSet );
737
738 for ( Iterator j = delayedPops.iterator(); j.hasNext(); )
739 {
740 JellyScriptHousing housing = (JellyScriptHousing) j.next();
741
742 reinstallPlugins( housing.getProject().getContext() );
743
744 housing.getProject().popContext();
745 }
746 delayedPops.clear();
747 }
748
749 /**
750 * Use the name of a goal to lookup all the plugins (that are stored in the plugin housings) that need to be
751 * executed in order to satisfy all the required preconditions for successful goal attainment.
752 *
753 * @param goalName the goal
754 * @param baseContext the base context to attain in
755 * @return a set of plugins required to attain the goal
756 * @throws Exception
757 * @todo don't throw Exception
758 */
759 public Set prepAttainGoal( String goalName, MavenJellyContext baseContext, GoalToJellyScriptHousingMapper goalMapper )
760 throws Exception
761 {
762 Set pluginSet = goalMapper.resolveJellyScriptHousings( goalName );
763
764 for ( Iterator j = pluginSet.iterator(); j.hasNext(); )
765 {
766 JellyScriptHousing housing = (JellyScriptHousing) j.next();
767 initialiseHousingPluginContext( housing, baseContext );
768 }
769 return pluginSet;
770 }
771
772 private MavenJellyContext initialiseHousingPluginContext( JellyScriptHousing housing, MavenJellyContext baseContext )
773 throws Exception
774 {
775 Project project = housing.getProject();
776
777
778
779 MavenUtils.integrateMapInContext( housing.getPluginProperties(), baseContext );
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804 MavenJellyContext pluginContext = new MavenJellyContext( baseContext );
805 project.pushContext( pluginContext );
806 pluginContext.setInherit( true );
807 pluginContext.setVariable( "context", pluginContext );
808 pluginContext.setVariable( "plugin", project );
809 pluginContext.setVariable( "plugin.dir", housing.getPluginDirectory() );
810 pluginContext.setVariable( "plugin.resources", new File( housing.getPluginDirectory(), "plugin-resources" ) );
811
812 LOGGER.debug( "initialising plugin housing: " + project );
813 runScript( housing, pluginContext );
814
815 return pluginContext;
816 }
817
818 /**
819 * Sets the pluginsDir attribute of the PluginManager object
820 *
821 * @param dir The maven plugin directory.
822 */
823 private void setPluginsDir( File dir )
824 {
825 pluginsDir = dir;
826 }
827
828 /**
829 * Retrieve the directory containing all plugins.
830 *
831 * @return The directory containing all plugins.
832 */
833 private File getPluginsDir()
834 {
835 return pluginsDir;
836 }
837
838 /**
839 * Sets the directory where the users plugins are located.
840 *
841 * @param dir The directory where the users plugins are located.
842 */
843 private void setUserPluginsDir( File dir )
844 {
845 userPluginsDir = dir;
846 }
847
848 /**
849 * Gets the directory where the user plugins are located.
850 *
851 * @return the directory where the user plugins are located.
852 */
853 private File getUserPluginsDir()
854 {
855 return userPluginsDir;
856 }
857
858 /**
859 * Sets the directory where the unpacked plugins are located.
860 *
861 * @param dir The directory where the unpacked plugins are located.
862 */
863 private void setUnpackedPluginsDir( File dir )
864 {
865 unpackedPluginsDir = dir;
866 }
867
868 /**
869 * Sets the directory where the unpacked plugins are located.
870 *
871 * @return the directory where the unpacked plugins are located.
872 */
873 private File getUnpackedPluginsDir()
874 {
875 return unpackedPluginsDir;
876 }
877
878 /**
879 * @return Set
880 */
881 public Set getGoalNames()
882 {
883 return mapper.getGoalNames();
884 }
885
886 /**
887 * Warning - this completely scrogs the default mapper. Only use this before System.exit!
888 * (currently used by maven -u).
889 *
890 * @return Set
891 * @todo refactor to return mapper instead and use that, or perhaps instantiate a new plugin manager
892 */
893 public Set getGoalNames( Project project )
894 throws MavenException
895 {
896 mapper = new GoalToJellyScriptHousingMapper();
897 readMavenXml( project, mapper );
898 return mapper.getGoalNames();
899 }
900
901 /**
902 */
903 public void installPlugin( File file, Project parentProject )
904 throws MavenException
905 {
906
907 installPlugin( file, parentProject, false );
908 }
909
910 /**
911 * Load and install a plugin.
912 *
913 * @param file the file to install. Must be a plugin jar
914 * @param parentProject the project to load the installed plugin into
915 */
916 public void installPlugin( File file, Project parentProject, boolean cache )
917 throws MavenException
918 {
919 LOGGER.debug( "Using plugin file: " + file );
920 try
921 {
922 String pluginName = file.getCanonicalFile().getName();
923 pluginName = pluginName.substring( 0, pluginName.indexOf( ".jar" ) );
924
925 if ( isLoaded( pluginName ) )
926 {
927
928 return;
929 }
930
931
932 File unpackedPluginDir = unpackPlugin( pluginName, file, cache );
933 if ( unpackedPluginDir != null )
934 {
935 JellyScriptHousing housing = createLazyPluginHousing( unpackedPluginDir );
936 if ( housing != null )
937 {
938 String artifactId = housing.getProject().getArtifactId();
939 if ( artifactIdToHousingMap.containsKey( artifactId ) )
940 {
941
942 JellyScriptHousing oldHousing = (JellyScriptHousing) artifactIdToHousingMap.get( artifactId );
943 LOGGER.debug( "Temporarily uninstalling: " + oldHousing );
944 addPluginToReinstall( parentProject.getContext(), artifactId, oldHousing );
945 pluginHousings.remove( oldHousing.getName() );
946 mapper.invalidatePlugin( oldHousing );
947 transientMapper.invalidatePlugin( oldHousing );
948 artifactIdToHousingMap.remove( artifactId );
949 }
950
951 mapArtifactIdToPluginHousing( artifactId, housing );
952 }
953 else
954 {
955 throw new MavenException( "Not a valid plugin file: " + file );
956 }
957
958 LOGGER.debug( "Installing plugin: " + housing );
959
960 housing.parse( transientMapper );
961
962 housing.parse( mapper );
963 if ( cache )
964 {
965 FileUtils.copyFileToDirectory( file, userPluginsDir );
966 cacheManager.registerPlugin( pluginName, housing );
967 housing.parse( cacheManager );
968 cacheManager.saveCache( unpackedPluginsDir );
969 }
970 }
971 else
972 {
973 throw new MavenException( "Not a valid JAR file: " + file );
974 }
975 }
976 catch ( IOException e )
977 {
978 throw new MavenException( "Error installing plugin", e );
979 }
980 }
981
982 /**
983 * @todo can this be removed and simplified if transient mapper contains the plugin mapping only?
984 */
985 private void addPluginToReinstall( MavenJellyContext context, String artifactId, JellyScriptHousing housing )
986 {
987 Map m = (Map) context.getVariables().get( PLUGIN_TEMP_MAP );
988 if ( m == null )
989 {
990 m = new HashMap();
991 context.setVariable( PLUGIN_TEMP_MAP, m );
992 }
993 m.put( artifactId, housing );
994 }
995
996 /**
997 * @todo can this be removed and simplified if transient mapper contains the plugin mapping only?
998 */
999 private void reinstallPlugins( MavenJellyContext context )
1000 throws MavenException
1001 {
1002 Map m = (Map) context.getVariables().get( PLUGIN_TEMP_MAP );
1003 if ( m != null )
1004 {
1005 for ( Iterator i = m.keySet().iterator(); i.hasNext(); )
1006 {
1007 String artifactId = (String) i.next();
1008 JellyScriptHousing housing = (JellyScriptHousing) m.get( artifactId );
1009 pluginHousings.remove( housing.getName() );
1010 mapper.invalidatePlugin( housing );
1011 transientMapper.invalidatePlugin( housing );
1012 housing = (JellyScriptHousing) m.get( artifactId );
1013 LOGGER.debug( "Reinstalling: " + housing );
1014 housing.parse( transientMapper );
1015 housing.parse( mapper );
1016 }
1017 }
1018 }
1019
1020 public void uninstallPlugin( String artifactId )
1021 throws IOException
1022 {
1023 LOGGER.debug( "Uninstalling plugin: " + artifactId );
1024
1025 JellyScriptHousing housing = (JellyScriptHousing) artifactIdToHousingMap.get( artifactId );
1026 if ( housing == null )
1027 {
1028 LOGGER.warn( "Plugin not found when attempting to uninstall '" + artifactId + "'" );
1029 return;
1030 }
1031
1032 String name = housing.getName();
1033 pluginHousings.remove( name );
1034 cacheManager.invalidateCache( name );
1035 mapper.invalidatePlugin( housing );
1036 transientMapper.invalidatePlugin( housing );
1037 artifactIdToHousingMap.remove( artifactId );
1038 cacheManager.saveCache( unpackedPluginsDir );
1039 }
1040
1041 /**
1042 * @param id
1043 * @return MavenJellyContext
1044 * @throws UnknownPluginException
1045 * @todo [1.0] refactor out, or make more appropriate structure
1046 * @todo remove throws Exception
1047 */
1048 public MavenJellyContext getPluginContext( String id )
1049 throws MavenException, UnknownPluginException
1050 {
1051 JellyScriptHousing housing = (JellyScriptHousing) artifactIdToHousingMap.get( id );
1052 if ( housing != null )
1053 {
1054 Project project = housing.getProject();
1055 if ( baseContext.equals( project.getContext().getParent() ) )
1056 {
1057 LOGGER.debug( "Plugin context for " + id + " already initialised for this base context" );
1058 return project.getContext();
1059 }
1060 else
1061 {
1062 LOGGER.debug( "Plugin context for " + id
1063 + " not initialised for this base context: initialising inside getPluginContext" );
1064 try
1065 {
1066 return initialiseHousingPluginContext( housing, baseContext );
1067 }
1068 catch ( Exception e )
1069 {
1070 throw new MavenException( "Error initialising plugin context", e );
1071 }
1072 }
1073 }
1074 throw new UnknownPluginException( id );
1075 }
1076
1077 public String getGoalDescription( String goalName )
1078 {
1079 return mapper.getGoalDescription( goalName );
1080 }
1081
1082 public void addDelayedPops( Set set )
1083 {
1084 delayedPops.addAll( set );
1085 }
1086
1087 /**
1088 * Unpack the plugin.
1089 *
1090 * @throws MavenException if there was a problem unpacking
1091 */
1092 File unpackPlugin( String pluginName, File jarFile, boolean cache )
1093 throws MavenException
1094 {
1095 File unzipDir = new File( unpackedPluginsDir, pluginName );
1096
1097
1098 boolean exists = unzipDir.exists();
1099 if ( !exists || ( jarFile.lastModified() > unzipDir.lastModified() ) )
1100 {
1101 if ( LOGGER.isDebugEnabled() )
1102 {
1103 LOGGER.debug( "Unpacking " + jarFile.getName() + " to directory --> " + unzipDir.getAbsolutePath() );
1104 }
1105
1106 if ( cache )
1107 {
1108 cacheManager.invalidateCache( pluginName );
1109 }
1110
1111 try
1112 {
1113 if ( exists )
1114 {
1115 FileUtils.deleteDirectory( unzipDir );
1116 }
1117
1118 Expand unzipper = new Expand();
1119 unzipper.setSrc( jarFile );
1120 unzipper.setDest( unzipDir );
1121 unzipper.execute();
1122 }
1123 catch ( IOException e )
1124 {
1125 throw new MavenException( "Unable to extract plugin: " + jarFile, e );
1126 }
1127 }
1128 return unzipDir;
1129 }
1130
1131 /**
1132 * @return Script
1133 * @todo get rid of throws Exception
1134 */
1135 private Script loadScript( JellyScriptHousing jellyScriptHousing )
1136 throws Exception
1137 {
1138
1139
1140
1141 if ( jellyScriptHousing.getPluginDirectory() != null )
1142 {
1143
1144
1145 jellyScriptHousing.getProject().verifyDependencies();
1146 processDependencies( jellyScriptHousing.getProject() );
1147 ClassLoader cl = jellyScriptHousing.getProject().getContext().getClassLoader();
1148 if ( cl instanceof ForeheadClassLoader )
1149 {
1150 ForeheadClassLoader pluginClassLoader = (ForeheadClassLoader) cl;
1151 pluginClassLoader.addURL( jellyScriptHousing.getPluginDirectory().toURL() );
1152 }
1153 }
1154
1155 MavenJellyContext context = jellyScriptHousing.getProject().getContext();
1156 URL oldRoot = context.getRootURL();
1157 URL oldCurrent = context.getCurrentURL();
1158
1159 context.setRootURL( jellyScriptHousing.getSource().toURL() );
1160 context.setCurrentURL( jellyScriptHousing.getSource().toURL() );
1161
1162 try
1163 {
1164 if ( LOGGER.isDebugEnabled() )
1165 {
1166 LOGGER.debug( "Jelly script to parse : " + jellyScriptHousing.getSource().toURL() );
1167 }
1168 Script script = JellyUtils.compileScript( jellyScriptHousing.getSource(), context );
1169
1170 context.setRootURL( oldRoot );
1171 context.setCurrentURL( oldCurrent );
1172
1173 return script;
1174 }
1175 catch ( Exception e )
1176 {
1177
1178 throw new MavenException( "Error parsing: " + jellyScriptHousing.getSource(), e );
1179 }
1180 }
1181
1182 /**
1183 * @param context
1184 * @throws Exception
1185 * @todo get rid of throws Exception
1186 */
1187 void runScript( JellyScriptHousing jellyScriptHousing, MavenJellyContext context )
1188 throws Exception
1189 {
1190 LOGGER.debug( "running script " + jellyScriptHousing.getSource() );
1191
1192 Script s = jellyScriptHousing.getScript();
1193 if ( s == null )
1194 {
1195 s = loadScript( jellyScriptHousing );
1196 jellyScriptHousing.setScript( s );
1197 }
1198 if ( context.getVariable( PLUGIN_HOUSING ) != null )
1199 {
1200 throw new IllegalStateException( "nested plugin housings" );
1201 }
1202 context.setVariable( PLUGIN_HOUSING, jellyScriptHousing );
1203 s.run( context, context.getXMLOutput() );
1204 context.removeVariable( PLUGIN_HOUSING );
1205 }
1206
1207 public Project getPluginProjectFromGoal( String goal )
1208 throws MavenException
1209 {
1210 JellyScriptHousing housing = mapper.getPluginHousing( goal );
1211 return housing != null ? housing.getProject() : null;
1212 }
1213
1214 public Collection getPluginList()
1215 {
1216 ArrayList list = new ArrayList();
1217 for ( Iterator i = pluginHousings.values().iterator(); i.hasNext(); )
1218 {
1219 JellyScriptHousing housing = (JellyScriptHousing) i.next();
1220 list.add( housing.getName() );
1221 }
1222 Collections.sort( list );
1223 return list;
1224 }
1225
1226 private void verifyUnpackedPluginsDir()
1227 throws MavenException
1228 {
1229 if ( !unpackedPluginsDir.exists() )
1230 {
1231 LOGGER.warn( getMessage( "directory.nonexistant.warning", unpackedPluginsDir ) );
1232
1233 if ( !unpackedPluginsDir.mkdirs() )
1234 {
1235 throw new MavenException( getMessage( "cannot.create.directory.warning", unpackedPluginsDir ) );
1236 }
1237 }
1238
1239 if ( !unpackedPluginsDir.isDirectory() )
1240 {
1241 throw new MavenException( getMessage( "not.directory.warning", unpackedPluginsDir ) );
1242 }
1243
1244 if ( !unpackedPluginsDir.canWrite() )
1245 {
1246 throw new MavenException( getMessage( "not.writable.warning", unpackedPluginsDir ) );
1247 }
1248 }
1249
1250 }