View Javadoc

1   package org.apache.maven.plugin;
2   
3   /* ====================================================================
4    *   Licensed to the Apache Software Foundation (ASF) under one or more
5    *   contributor license agreements.  See the NOTICE file distributed with
6    *   this work for additional information regarding copyright ownership.
7    *   The ASF licenses this file to You under the Apache License, Version 2.0
8    *   (the "License"); you may not use this file except in compliance with
9    *   the License.  You may obtain a copy of the License at
10   *
11   *       http://www.apache.org/licenses/LICENSE-2.0
12   *
13   *   Unless required by applicable law or agreed to in writing, software
14   *   distributed under the License is distributed on an "AS IS" BASIS,
15   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   *   See the License for the specific language governing permissions and
17   *   limitations under the License.
18   * ====================================================================
19   */
20  
21  import java.util.Arrays;
22  import java.util.Collection;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.Iterator;
26  import java.util.LinkedList;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.maven.util.InsertionOrderedSet;
34  import org.apache.maven.werkz.CyclicGoalChainException;
35  import org.apache.maven.werkz.Goal;
36  import org.apache.maven.werkz.NoSuchGoalException;
37  import org.apache.maven.werkz.WerkzProject;
38  import org.codehaus.plexus.util.StringUtils;
39  
40  /**
41   * This is the process that we use:
42   *
43   * - Start with goal X
44   * - Gather prereqs for X
45   *
46   * We want to find all goals that will be executed due to X being executed
47   * we don't care about ordering here because when werkz executes it will sort
48   * that out for us. We just need to make sure all the appropriate plugins have
49   * been loaded so that werkz can find the goals it needs.
50   *
51   * For a single goal we will collect the set of goals that will be executed with
52   * it.
53   */
54  public class GoalToJellyScriptHousingMapper
55      implements PluginDefinitionHandler
56  {
57      /** Logger */
58      private static final Log LOGGER = LogFactory.getLog( GoalToJellyScriptHousingMapper.class );    
59      
60      /** dyna tag -> plugin map. Where dyna tags live. */
61      private HashMap dynaTagPluginMap = new HashMap();
62  
63      /** A plugins dyna tag dependencies. */
64      private HashMap pluginDynaTagDepsMap = new HashMap();
65  
66      /** Goal -> plugin map. */
67      private HashMap goalPluginMap = new HashMap();
68  
69      /** Goals with a pointer to plugins with callbacks. */
70      private HashMap preGoalDecoratorsMap = new HashMap();
71  
72      /** Goals with a pointer to plugins with callbacks. */
73      private HashMap postGoalDecoratorsMap = new HashMap();
74  
75      /** */
76      private final WerkzProject goalProject = new WerkzProject();
77  
78      /** */
79      private String defaultGoalName;
80  
81      private final HashSet resolvedPlugins = new HashSet();
82  
83      /**
84       * Default constructor.
85       */
86      public GoalToJellyScriptHousingMapper()
87      {
88      }
89  
90      /**
91       * Merge parent mapper. Used to setup a transient submapper.
92       *
93       * @param mapper mapper to copy from
94       * @todo figure out which are actually needed, change redundant initialisers by removing here,
95       * or adding others to default constructor (eg goalProject init)
96       * @todo We should have a way to inherit entire werkz project instead of hokey goal copying
97       */
98      void merge( GoalToJellyScriptHousingMapper mapper )
99          throws CyclicGoalChainException
100     {
101         mergeMap( dynaTagPluginMap, mapper.dynaTagPluginMap );
102         mergeMap( pluginDynaTagDepsMap, mapper.pluginDynaTagDepsMap );
103         mergeMap( goalPluginMap, mapper.goalPluginMap );
104 
105         for ( Iterator i = mapper.preGoalDecoratorsMap.keySet().iterator(); i.hasNext(); )
106         {
107             String goalName = (String) i.next();
108             Set s = (Set) mapper.preGoalDecoratorsMap.get( goalName );
109             Set sExist = (Set) preGoalDecoratorsMap.get( goalName );
110             if ( sExist == null )
111             {
112                 preGoalDecoratorsMap.put( goalName, s );
113             }
114             else
115             {
116                 sExist.addAll( s );
117             }
118         }
119         for ( Iterator i = mapper.postGoalDecoratorsMap.keySet().iterator(); i.hasNext(); )
120         {
121             String goalName = (String) i.next();
122             Set s = (Set) mapper.postGoalDecoratorsMap.get( goalName );
123             Set sExist = (Set) postGoalDecoratorsMap.get( goalName );
124             if ( sExist == null )
125             {
126                 postGoalDecoratorsMap.put( goalName, s );
127             }
128             else
129             {
130                 sExist.addAll( s );
131             }
132         }
133 
134         if ( defaultGoalName == null )
135         {
136             defaultGoalName = mapper.defaultGoalName;
137         }
138 
139         for ( Iterator i = mapper.goalProject.getGoals().iterator(); i.hasNext(); )
140         {
141             Goal goal = (Goal) i.next();
142             Goal existingGoal = goalProject.getGoal( goal.getName() );
143             if ( existingGoal == null )
144             {
145                 goalProject.addGoal( goal );
146             }
147             else
148             {
149                 for ( Iterator j = goal.getPrecursors().iterator(); j.hasNext(); )
150                 {
151                     existingGoal.addPrecursor( (Goal) j.next() );
152                 }
153                 for ( Iterator j = goal.getPostcursors().iterator(); j.hasNext(); )
154                 {
155                     existingGoal.addPostcursor( (Goal) j.next() );
156                 }
157             }
158         }
159         resolvedPlugins.addAll( mapper.resolvedPlugins );
160     }
161 
162     /**
163      * merge source into target, but don't override anything. Kind of the
164      * opposite of putAll to some extent.
165      *
166      * @todo Surely can be more efficient.
167      * @todo if there isn't already a util function for this there should be.
168      */
169     private static void mergeMap( Map target, Map source )
170     {
171         Map map = new HashMap( target );
172         target.putAll( source );
173         target.putAll( map );
174     }
175 
176     // ----------------------------------------------------------------------
177     // Accessors
178     // ----------------------------------------------------------------------
179 
180     public String getDefaultGoalName()
181     {
182         return defaultGoalName;
183     }
184 
185     public void setDefaultGoalName( String defaultGoalName )
186     {
187         // first definition wins
188         if ( this.defaultGoalName == null )
189         {
190             this.defaultGoalName = defaultGoalName;
191         }
192     }
193 
194     // ----------------------------------------------------------------------
195     // Implementation
196     // ----------------------------------------------------------------------
197 
198     public JellyScriptHousing getPluginHousing( String goal )
199     {
200         return (JellyScriptHousing) goalPluginMap.get( goal );
201     }
202 
203     private HashSet getPluginDynaTagDeps( Object pluginHousing )
204     {
205         HashSet pluginDynaTagDeps = (HashSet) pluginDynaTagDepsMap.get( pluginHousing );
206 
207         if ( pluginDynaTagDeps == null )
208         {
209             pluginDynaTagDeps = new HashSet();
210             pluginDynaTagDepsMap.put( pluginHousing, pluginDynaTagDeps );
211         }
212 
213         return pluginDynaTagDeps;
214     }
215 
216     HashSet getPostGoalDecorators( String goalName )
217     {
218         HashSet decorators = (HashSet) postGoalDecoratorsMap.get( goalName );
219 
220         if ( decorators == null )
221         {
222             decorators = new HashSet();
223             postGoalDecoratorsMap.put( goalName, decorators );
224         }
225         return decorators;
226     }
227 
228     HashSet getPreGoalDecorators( String goalName )
229     {
230         HashSet decorators = (HashSet) preGoalDecoratorsMap.get( goalName );
231 
232         if ( decorators == null )
233         {
234             decorators = new HashSet();
235             preGoalDecoratorsMap.put( goalName, decorators );
236         }
237         return decorators;
238     }
239 
240     /**
241      * Find the appropriate plugins that provide the give goal and its precursors.
242      * Goals such as ${report}:register will need to be resolved lazily.
243      *
244      * @param goal the goal to find
245      * @return the set of plugins
246      * @throws NoSuchGoalException if the given goal is in no plugins
247      * @see org.apache.maven.jelly.tags.werkz.MavenAttainGoalTag
248      */
249     Set resolveJellyScriptHousings( String goal )
250         throws NoSuchGoalException
251     {
252         LOGGER.debug( "preparing goal: " + goal );
253 
254         Set pluginSet = new InsertionOrderedSet();
255 
256         Goal[] chain = getExecutionChain( goal, goalProject );
257         LOGGER.debug( "execution chain: " + Arrays.asList( chain ).toString() );
258 
259         for ( int i = 0; i < chain.length; i++ )
260         {
261             Goal g = chain[i];
262             Object plugin = goalPluginMap.get( g.getName() );
263             if ( plugin == null )
264             {
265                 throw new NoSuchGoalException( g.getName() );
266             }
267             pluginSet.add( plugin );
268             Collection decorators = getPostGoalDecorators( g.getName() );
269             if ( LOGGER.isDebugEnabled() && !decorators.isEmpty() )
270             {
271                 LOGGER.debug( "goal " + g.getName() + " has postGoal decorators " + decorators );
272             }
273             pluginSet.addAll( decorators );
274             decorators = getPreGoalDecorators( g.getName() );
275             if ( LOGGER.isDebugEnabled() && !decorators.isEmpty() )
276             {
277                 LOGGER.debug( "goal " + g.getName() + " has preGoal decorators " + decorators );
278             }
279             pluginSet.addAll( decorators );
280         }
281 
282         // Done like this as the dynatag plugin dependencies must be first in the list
283         InsertionOrderedSet newPluginSet = resolveDynaTagPlugins( pluginSet );
284         if ( LOGGER.isDebugEnabled() && !newPluginSet.isEmpty() )
285         {
286             LOGGER.debug( "dynatag dependencies: " + newPluginSet );
287         }
288         newPluginSet.addAll( pluginSet );
289         pluginSet = newPluginSet;
290 
291         // Here we make the resolved plugins field be the full set of plugins, and the plugins returned just the
292         // additional ones resolved.
293         // TODO: skip the remove step - don't add if it is already loaded
294         pluginSet.removeAll( resolvedPlugins );
295         resolvedPlugins.addAll( pluginSet );
296 
297         LOGGER.debug( "final list of plugins to prepare: " + pluginSet );
298 
299         return pluginSet;
300     }
301 
302     /**
303      * @todo [1.0] use werkz beta-11 version instead
304      */
305     public Goal[] getExecutionChain( String name, WerkzProject project )
306         throws NoSuchGoalException
307     {
308         Goal goal = project.getGoal( name );
309 
310         LinkedList chain = new LinkedList();
311         LinkedList stack = new LinkedList();
312 
313         stack.addLast( goal );
314 
315         while ( !stack.isEmpty() )
316         {
317             goal = (Goal) stack.removeFirst();
318 
319             if ( ( goal == null ) || chain.contains( goal ) )
320             {
321                 continue;
322             }
323 
324             chain.addFirst( goal );
325 
326             List precursors = goal.getPrecursors();
327 
328             for ( Iterator i = precursors.iterator(); i.hasNext(); )
329             {
330                 Goal eachPrecursor = (Goal) i.next();
331 
332                 if ( !chain.contains( eachPrecursor ) )
333                 {
334                     stack.addLast( eachPrecursor );
335                 }
336             }
337         }
338 
339         return (Goal[]) chain.toArray( Goal.EMPTY_ARRAY );
340     }
341 
342     /**
343      * Resolve plugins that provide dynamic tags for the given set of plugins.
344      *
345      * @param plugins the plugins containing tags
346      * @return the plugins providing tags, in insertion order
347      */
348     private InsertionOrderedSet resolveDynaTagPlugins( Set plugins )
349     {
350         // Important to return an insertion ordered set as the calling function is going to add to it and
351         // depends on these remaining first
352         InsertionOrderedSet resolvedDynaTagPlugins = new InsertionOrderedSet();
353 
354         for ( Iterator i = plugins.iterator(); i.hasNext(); )
355         {
356             JellyScriptHousing plugin = (JellyScriptHousing) i.next();
357             Set dynaTagDeps = getPluginDynaTagDeps( plugin );
358 
359             for ( Iterator j = dynaTagDeps.iterator(); j.hasNext(); )
360             {
361                 LinkedList dynaTagUris = new LinkedList();
362                 Set seen = new HashSet();
363 
364                 Object next = j.next();
365                 dynaTagUris.add( next );
366 
367                 while ( !dynaTagUris.isEmpty() )
368                 {
369                     String dynaTagUri = (String) dynaTagUris.removeFirst();
370 
371                     if ( seen.contains( dynaTagUri ) )
372                     {
373                         continue;
374                     }
375 
376                     seen.add( dynaTagUri );
377 
378                     Object dynaTagPluginHome = dynaTagPluginMap.get( dynaTagUri );
379                     if ( dynaTagPluginHome == null )
380                     {
381                         // This is essentially allowed for bootstrap so
382                         // plugins can import taglibs they don't use until later
383                         LOGGER.warn( "Tag library requested that is not present: '" + dynaTagUri + "' in plugin: '"
384                             + plugin.getName() + "'" );
385                     }
386                     else
387                     {
388                         Set set = getPluginDynaTagDeps( dynaTagPluginHome );
389                         dynaTagUris.addAll( set );
390                         resolvedDynaTagPlugins.add( dynaTagPluginHome );
391                     }
392                 }
393             }
394         }
395         return resolvedDynaTagPlugins;
396     }
397 
398     // ----------------------------------------------------------------------
399     // Handler implementation
400     // ----------------------------------------------------------------------
401 
402     public void addPluginDynaTagDep( JellyScriptHousing housing, String uri )
403     {
404         getPluginDynaTagDeps( housing ).add( uri );
405     }
406 
407     /* (non-Javadoc)
408      * @see org.apache.maven.plugin.PluginDefintionHandler#removePluginDynaTagDep(org.apache.maven.plugin.JellyScriptHousing, java.lang.String)
409      */
410     public void removePluginDynaTagDep( JellyScriptHousing housing, String uri )
411     {
412         getPluginDynaTagDeps( housing ).remove( uri );
413     }
414 
415     public void addDynaTagLib( String tagLibUri, JellyScriptHousing jellyScriptHousing )
416     {
417         dynaTagPluginMap.put( tagLibUri, jellyScriptHousing );
418     }
419 
420     public void addPostGoal( String name, JellyScriptHousing jellyScriptHousing )
421     {
422         getPostGoalDecorators( name ).add( jellyScriptHousing );
423     }
424 
425     public void addPreGoal( String name, JellyScriptHousing jellyScriptHousing )
426     {
427         getPreGoalDecorators( name ).add( jellyScriptHousing );
428     }
429 
430     public void addGoal( String name, String prereqs, String description, JellyScriptHousing jellyScriptHousing )
431     {
432         // We load plugins in order of priority, so don't add goals that already exist.
433         if ( !goalPluginMap.containsKey( name ) )
434         {
435             Goal goal = goalProject.getGoal( name, true );
436 
437             goal.setDescription( description );
438 
439             goalProject.addGoal( goal );
440 
441             // Add to the goal -> plugin map.
442             goalPluginMap.put( name, jellyScriptHousing );
443 
444             if ( prereqs != null )
445             {
446                 String[] s = StringUtils.split( prereqs, "," );
447 
448                 for ( int i = 0; i < s.length; i++ )
449                 {
450                     try
451                     {
452                         Goal prereq = goalProject.getGoal( s[i].trim(), true );
453                         goal.addPrecursor( prereq );
454                     }
455                     catch ( CyclicGoalChainException e )
456                     {
457                         // do nothing.
458                     }
459                 }
460             }
461         }
462     }
463 
464     /**
465      * @return goal names
466      */
467     Set getGoalNames()
468     {
469         return goalPluginMap.keySet();
470     }
471 
472     String getGoalDescription( String goalName )
473     {
474         Goal goal = goalProject.getGoal( goalName );
475         String goalDescription = ( goal != null ? goal.getDescription() : null );
476         if ( goalDescription != null )
477         {
478             goalDescription = goalDescription.trim();
479             if ( "null".equals( goalDescription ) )
480             {
481                 goalDescription = null;
482             }
483             else if ( goalDescription.length() == 0 )
484             {
485                 goalDescription = null;
486             }
487         }
488         return goalDescription;
489     }
490 
491     void addResolvedPlugins( List projectHousings )
492     {
493         resolvedPlugins.addAll( projectHousings );
494     }
495 
496     void clearResolvedPlugins()
497     {
498         resolvedPlugins.clear();
499     }
500 
501     void invalidatePlugin( JellyScriptHousing housing )
502     {
503         resolvedPlugins.remove( housing );
504         for ( Iterator i = dynaTagPluginMap.keySet().iterator(); i.hasNext(); )
505         {
506             String uri = (String) i.next();
507             if ( dynaTagPluginMap.get( uri ).equals( housing ) )
508             {
509                 i.remove();
510             }
511         }
512         pluginDynaTagDepsMap.remove( housing );
513 
514         for ( Iterator i = goalPluginMap.keySet().iterator(); i.hasNext(); )
515         {
516             String goal = (String) i.next();
517             if ( goalPluginMap.get( goal ).equals( housing ) )
518             {
519                 i.remove();
520                 goalProject.removeGoal( goal );
521             }
522         }
523         for ( Iterator i = preGoalDecoratorsMap.keySet().iterator(); i.hasNext(); )
524         {
525             String preGoal = (String) i.next();
526             Set decorators = (Set) preGoalDecoratorsMap.get( preGoal );
527             decorators.remove( housing );
528         }
529         for ( Iterator i = postGoalDecoratorsMap.keySet().iterator(); i.hasNext(); )
530         {
531             String postGoal = (String) i.next();
532             Set decorators = (Set) postGoalDecoratorsMap.get( postGoal );
533             decorators.remove( housing );
534         }
535     }
536 }