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.io.File;
22  import java.io.FileInputStream;
23  import java.io.FileNotFoundException;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Properties;
32  import java.util.StringTokenizer;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.maven.MavenException;
37  import org.apache.maven.MavenSession;
38  import org.codehaus.plexus.util.StringUtils;
39  
40  /**
41   * Plugin cache management.
42   *
43   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
44   */
45  final class PluginCacheManager
46      implements PluginDefinitionHandler
47  {
48      /** Log. */
49      private static final Log LOGGER = LogFactory.getLog( PluginCacheManager.class );
50  
51      /** Maven session LOGGER */
52      private static final Log sessionLog = LogFactory.getLog( MavenSession.class );
53  
54      /** Plug-in cache lock. */
55      public static final String LOCK_CACHE = "lock.cache";
56  
57      /** Plug-in cache valid. */
58      public static final String VALID_CACHE = "valid.cache";
59  
60      /** Artifact ID to Plug-in cache */
61      public static final String ARTIFACT_ID_CACHE = "artifactIdToPlugin.cache";
62  
63      /** Plug-in cache */
64      public static final String PLUGINS_CACHE = "plugins.cache";
65  
66      /** Goal cache */
67      public static final String GOALS_CACHE = "goals.cache";
68  
69      /** call backs cache */
70      public static final String CALLBACKS_CACHE = "callbacks.cache";
71  
72      /** Taglibs cache */
73      public static final String DYNAMIC_TAGLIBS_CACHE = "dynatag.cache";
74  
75      /** Plugin -&gt; dynatag dependencies. */
76      public static final String PLUGIN_DYNATAG_DEPS_CACHE = "plugin-dynatag-deps.cache";
77  
78      /** Dirty flag. */
79      private boolean dirty = true;
80  
81      /**
82       * The artifact id cache contains plugin name to artifact ID mappings.
83       * @todo just for getPluginContext - should not be loading multiple versions, and hence using artifact ID instead of name everywhere...
84       */
85      private Properties artifactIdCache = new Properties();
86  
87      /**
88       * The goals caches contains a mapping of goal names to a description for the
89       * goal and any prerequisite goals.
90       */
91      private Properties goalCache = new Properties();
92  
93      /**
94       * The plugin cache contains a mapping of goal names to plugin names.
95       */
96      private Properties pluginCache = new Properties();
97  
98      /**
99       * Dyanamic tag libraries that are created within plugin.jelly scripts using
100      * the jelly:define tags. So we might have something like the following:
101      * <pre>
102      * &lt;define:taglib uri="aptdoc"&gt;
103      *   &lt;define:jellybean
104      *     name="convert"
105      *     className="org.apache.maven.aptdoc.AptToXdocConverter"
106      *     method="doExecute"
107      *   /&gt;
108      * &lt;/define:taglib&gt;
109      * </pre>
110      * What will end up in the dynamic-taglibs.cache file is something like
111      * the following:
112      *
113      * aptdoc=maven-aptdoc-1.0-SNAPSHOT
114      */
115     private Properties dynaTagLibCache = new Properties();
116 
117     /**
118      * This is list of registered preGoals and postGoals in the various plugins. For
119      * example the antlr plugin has preGoal set on java:compile goal to make sure that
120      * that antlr can generate the requested sources before java:compile executes. In
121      * this case we have something like the following registered in the callbacks.cache
122      * file:
123      *
124      * java\:compile.pre=maven-antlr-plugin[1.1-SNAPSHOT;1.2]
125      *
126      * Note that ":" is a key termination character to the Properties class so we
127      * escape it to be identified as a ":" character.
128      */
129     private Properties callbackCache = new Properties();
130 
131     /**
132      * Plugin dependencies on dyna tags. So, for example, the junit plugin
133      * depends on the 'doc' dynatag which provides the mechanism to perform
134      * a JSL transformation.
135      */
136     private Properties pluginDynaTagDepsCache = new Properties();
137 
138     /** Default constructor. */
139     public PluginCacheManager()
140     {
141     }
142 
143     void checkLockFile( File lockFile )
144     {
145         for ( int i = 1; i <= 10; i++ )
146         {
147             if ( lockFile.exists() )
148             {
149                 LOGGER.info( "Lock file " + lockFile + " exists, waiting... " + i + " of 10" );
150                 try
151                 {
152                     Thread.sleep( 1000 );
153                 }
154                 catch ( InterruptedException e )
155                 {
156                     // do nothing
157                 }
158             }
159             else
160             {
161                 break;
162             }
163         }
164         if ( lockFile.exists() )
165         {
166             LOGGER.warn( "Lock file still exists: ignoring" );
167             lockFile.delete();
168         }
169     }
170 
171     void saveCache( File directory )
172         throws IOException
173     {
174         if ( !dirty )
175         {
176             return;
177         }
178 
179         directory.mkdirs();
180 
181         File lockFile = new File( directory, LOCK_CACHE );
182         checkLockFile( lockFile );
183 
184         LOGGER.debug( "Locking " + lockFile );
185         if ( !lockFile.getParentFile().exists() )
186         {
187             lockFile.getParentFile().mkdirs();
188         }
189         lockFile.createNewFile();
190 
191         try
192         {
193             if ( LOGGER.isDebugEnabled() )
194             {
195                 LOGGER.debug( "Saving caches to " + directory.getAbsolutePath() );
196             }
197 
198             File f = new File( directory, VALID_CACHE );
199             f.delete();
200             storeProperties( pluginCache, new File( directory, PLUGINS_CACHE ), "plugins cache" );
201             storeProperties( goalCache, new File( directory, GOALS_CACHE ), "goals cache" );
202             storeProperties( callbackCache, new File( directory, CALLBACKS_CACHE ), "callbacks cache" );
203             storeProperties( dynaTagLibCache, new File( directory, DYNAMIC_TAGLIBS_CACHE ), "taglibs cache" );
204             storeProperties( pluginDynaTagDepsCache, new File( directory, PLUGIN_DYNATAG_DEPS_CACHE ),
205                              "plugin deps cache" );
206             storeProperties( artifactIdCache, new File( directory, ARTIFACT_ID_CACHE ), "artifact ID to plugin mapping" );
207             f.createNewFile();
208             dirty = false;
209         }
210         finally
211         {
212             LOGGER.debug( "Unlocking " + lockFile );
213             lockFile.delete();
214         }
215     }
216 
217     /**
218      * Write the given properties to the given file in the unpacked dir
219      * @param properties the properties object to store
220      * @param header the header for the written properties
221      * @throws FileNotFoundException when the unpacked plugin directory can't be found
222      * @throws IOException if there is a problem storing properties
223      */
224     private void storeProperties( Properties properties, File file, String header )
225         throws FileNotFoundException, IOException
226     {
227         FileOutputStream stream = new FileOutputStream( file );
228         properties.store( stream, header );
229         stream.close();
230     }
231 
232     /**
233      * @return a loaded properties file from disk or a new one if there are any problems
234      * @param file the file within the unpacked plugins dir to load
235      */
236     private Properties loadProperties( File file )
237         throws IOException
238     {
239         Properties properties = new Properties();
240         FileInputStream stream = null;
241         try
242         {
243             stream = new FileInputStream( file );
244             properties.load( stream );
245         }
246         finally
247         {
248             if ( stream != null )
249             {
250                 try
251                 {
252                     stream.close();
253                 }
254                 catch ( IOException e )
255                 {
256                     // Nothing to do
257                 }
258             }
259         }
260         return properties;
261     }
262 
263     /**
264      *  Load on-disk cache information, if possible.
265      */
266     void loadCache( File directory )
267     {
268         File lockFile = new File( directory, LOCK_CACHE );
269         checkLockFile( lockFile );
270 
271         File f = new File( directory, VALID_CACHE );
272         if ( !f.exists() )
273         {
274             LOGGER.info( "Plugin cache will be regenerated" );
275             return;
276         }
277         LOGGER.debug( "Loading plugin cache" );
278         try
279         {
280             pluginCache = loadProperties( new File( directory, PLUGINS_CACHE ) );
281             goalCache = loadProperties( new File( directory, GOALS_CACHE ) );
282             callbackCache = loadProperties( new File( directory, CALLBACKS_CACHE ) );
283             dynaTagLibCache = loadProperties( new File( directory, DYNAMIC_TAGLIBS_CACHE ) );
284             pluginDynaTagDepsCache = loadProperties( new File( directory, PLUGIN_DYNATAG_DEPS_CACHE ) );
285             artifactIdCache = loadProperties( new File( directory, ARTIFACT_ID_CACHE ) );
286         }
287         catch ( IOException e )
288         {
289             LOGGER.warn( "Clearing cache due to exception loading part of cache" );
290             LOGGER.debug( "Exception", e );
291             clearCache();
292         }
293         dirty = false;
294     }
295 
296     /**
297      * Manipulate a csv property string.
298      *
299      * @param properties
300      * @param key
301      * @param newEntry
302      */
303     private void appendCsvProperty( Properties properties, String key, String newEntry )
304     {
305         String csvProperty = properties.getProperty( key );
306 
307         if ( csvProperty == null )
308         {
309             properties.setProperty( key, newEntry );
310         }
311         else
312         {
313             StringTokenizer st = new StringTokenizer( csvProperty, "," );
314 
315             while ( st.hasMoreTokens() )
316             {
317                 if ( st.nextToken().equals( newEntry ) )
318                 {
319                     return;
320                 }
321             }
322             // not already there so append and return
323             properties.setProperty( key, csvProperty + ',' + newEntry );
324         }
325     }
326 
327     public void addPluginDynaTagDep( JellyScriptHousing housing, String uri )
328     {
329         appendCsvProperty( pluginDynaTagDepsCache, housing.getName(), uri );
330         dirty = true;
331     }
332 
333     public void removePluginDynaTagDep( JellyScriptHousing housing, String uri )
334     {
335         String prop = (String) pluginDynaTagDepsCache.get( housing.getName() );
336         int tl = uri.length();
337 
338         if ( prop.indexOf( "," ) < 0 )
339         {
340             pluginDynaTagDepsCache.remove( housing.getName() );
341         }
342         else
343         {
344             int i = prop.indexOf( uri + "," );
345             if ( i == 0 )
346             {
347                 // First
348                 prop = prop.substring( tl + 1 );
349             }
350             else if ( i > 0 )
351             {
352                 // Middle
353                 prop = prop.substring( 0, i ) + prop.substring( i + tl + 1 );
354             }
355             else
356             {
357                 // The last entry
358                 prop = prop.substring( 0, prop.length() - tl - 1 );
359             }
360             if ( LOGGER.isDebugEnabled() )
361             {
362                 LOGGER.debug( "Caching Taglib Dependency --> " + prop );
363             }
364             pluginDynaTagDepsCache.put( housing.getName(), prop );
365         }
366         dirty = true;
367     }
368 
369     void registerPlugin( String name, JellyScriptHousing housing )
370         throws MavenException
371     {
372         artifactIdCache.put( name, housing.getProject().getArtifactId() );
373     }
374 
375     public void addPostGoal( String name, JellyScriptHousing housing )
376     {
377         appendCsvProperty( callbackCache, name + ".post", housing.getName() );
378         dirty = true;
379     }
380 
381     public void addPreGoal( String name, JellyScriptHousing housing )
382     {
383         appendCsvProperty( callbackCache, name + ".pre", housing.getName() );
384         dirty = true;
385     }
386 
387     public void addGoal( String name, String prereqs, String description, JellyScriptHousing housing )
388     {
389         String goalProperty = description + ">";
390 
391         if ( prereqs != null )
392         {
393             goalProperty += StringUtils.deleteWhitespace( prereqs );
394         }
395 
396         goalCache.setProperty( name, goalProperty );
397         pluginCache.setProperty( name, housing.getName() );
398         dirty = true;
399     }
400 
401     public void addDynaTagLib( String tagLibUri, JellyScriptHousing housing )
402     {
403         dynaTagLibCache.setProperty( tagLibUri, housing.getName() );
404         dirty = true;
405     }
406 
407     /**
408      * Invalidate cache information for a single plugin.
409      *
410      * @param pluginName The name of the plugin to invalid cache entries.
411      */
412     void invalidateCache( String pluginName )
413     {
414         LOGGER.debug( "Invalidating plugin " + pluginName );
415         for ( Iterator i = dynaTagLibCache.keySet().iterator(); i.hasNext(); )
416         {
417             String uri = (String) i.next();
418 
419             if ( dynaTagLibCache.getProperty( uri ).equals( pluginName ) )
420             {
421                 sessionLog.debug( "removing dynataglib cache entry for uri " + uri );
422                 i.remove();
423             }
424         }
425         for ( Iterator i = callbackCache.keySet().iterator(); i.hasNext(); )
426         {
427             String callbackName = (String) i.next();
428             String goalName = callbackName.endsWith( ".pre" ) ? callbackName.substring( 0, callbackName.length() - 4 )
429                                                              : callbackName.substring( 0, callbackName.length() - 5 );
430 
431             // This may be null as you can define pre/postGoals on goals that don't (yet) exist
432             String callbackPlugin = pluginCache.getProperty( goalName );
433             String callbacks = callbackCache.getProperty( callbackName );
434             if ( pluginName.equals( callbackPlugin ) )
435             {
436                 sessionLog
437                     .debug( "removing callback cache entry for goal " + callbackName + " (goal in source plugin)" );
438                 i.remove();
439             }
440             else if ( pluginName.equals( callbacks ) )
441             {
442                 sessionLog
443                     .debug( "removing callback cache entry for goal " + callbackName + " (lone callback removed)" );
444                 i.remove();
445             }
446             else
447             {
448                 StringTokenizer tok = new StringTokenizer( callbacks, "," );
449                 StringBuffer newCallback = new StringBuffer();
450                 boolean changed = false;
451                 while ( tok.hasMoreTokens() )
452                 {
453                     String name = tok.nextToken();
454                     if ( name.equals( pluginName ) )
455                     {
456                         changed = true;
457                     }
458                     else
459                     {
460                         if ( newCallback.length() > 0 )
461                         {
462                             newCallback.append( "," );
463                         }
464                         newCallback.append( name );
465                     }
466                 }
467                 if ( changed )
468                 {
469                     callbacks = newCallback.toString();
470                     sessionLog.debug( "removing callback cache value for goal " + callbackName
471                         + " (other callbacks remain: " + callbacks + ")" );
472                     callbackCache.setProperty( callbackName, callbacks );
473                 }
474             }
475         }
476         for ( Iterator i = goalCache.keySet().iterator(); i.hasNext(); )
477         {
478             String eachGoal = (String) i.next();
479 
480             if ( pluginCache.getProperty( eachGoal ).equals( pluginName ) )
481             {
482                 sessionLog.debug( "removing goal and plugin cache entry for goal " + eachGoal );
483                 i.remove();
484                 pluginCache.remove( eachGoal );
485             }
486         }
487         if ( pluginDynaTagDepsCache.containsKey( pluginName ) )
488         {
489             sessionLog.debug( "removing dynatag dependency cache entry" );
490             pluginDynaTagDepsCache.remove( pluginName );
491         }
492         if ( artifactIdCache.containsKey( pluginName ) )
493         {
494             sessionLog.debug( "removing artifactId cache entry" );
495             artifactIdCache.remove( pluginName );
496         }
497 
498         dirty = true;
499     }
500 
501     private JellyScriptHousing loadHousing( String pluginName, PluginManager manager, Map pluginDirs, String desc )
502         throws IOException
503     {
504         File dir = (File) pluginDirs.get( pluginName );
505         if ( dir == null )
506         {
507             sessionLog.warn( "plugin " + pluginName + " is cached (" + desc + ") but no longer present" );
508             return null;
509         }
510 
511         JellyScriptHousing housing = manager.loadPluginHousing( pluginName, dir );
512         if ( housing == null )
513         {
514             sessionLog.error( "plugin " + pluginName + " is cached (" + desc + ") but no longer valid" );
515             return null;
516         }
517         return housing;
518     }
519 
520     private void clearCache()
521     {
522         pluginCache.clear();
523         callbackCache.clear();
524         goalCache.clear();
525         dynaTagLibCache.clear();
526         pluginDynaTagDepsCache.clear();
527         artifactIdCache.clear();
528     }
529 
530     /**
531      * @param mapper
532      */
533     boolean mapPlugins( GoalToJellyScriptHousingMapper mapper, PluginManager manager, Map pluginDirs )
534         throws IOException
535     {
536         // Must invalidate bad ones first to keep iterators and cache consistent
537         for ( Iterator i = pluginDynaTagDepsCache.keySet().iterator(); i.hasNext(); )
538         {
539             String pluginName = (String) i.next();
540             JellyScriptHousing housing = loadHousing( pluginName, manager, pluginDirs, "dynatag dep" );
541             if ( housing == null )
542             {
543                 clearCache();
544                 return false;
545             }
546         }
547         for ( Iterator i = pluginCache.keySet().iterator(); i.hasNext(); )
548         {
549             String goalName = (String) i.next();
550             String pluginName = pluginCache.getProperty( goalName );
551             JellyScriptHousing housing = loadHousing( pluginName, manager, pluginDirs, "goal" );
552             if ( housing == null )
553             {
554                 clearCache();
555                 return false;
556             }
557         }
558         for ( Iterator i = artifactIdCache.keySet().iterator(); i.hasNext(); )
559         {
560             String pluginName = (String) i.next();
561             JellyScriptHousing housing = loadHousing( pluginName, manager, pluginDirs, "artifactId" );
562             if ( housing == null )
563             {
564                 clearCache();
565                 return false;
566             }
567         }
568 
569         Map preGoals = new HashMap();
570         Map postGoals = new HashMap();
571         for ( Iterator i = callbackCache.keySet().iterator(); i.hasNext(); )
572         {
573             String callbackName = (String) i.next();
574             boolean isPreGoal = callbackName.endsWith( ".pre" );
575             String goalName = isPreGoal ? callbackName.substring( 0, callbackName.length() - 4 ) : callbackName
576                 .substring( 0, callbackName.length() - 5 );
577             String pluginNames = callbackCache.getProperty( callbackName );
578             StringTokenizer tok = new StringTokenizer( pluginNames, "," );
579             List housings = new ArrayList();
580             if ( isPreGoal )
581             {
582                 preGoals.put( goalName, housings );
583             }
584             else
585             {
586                 postGoals.put( goalName, housings );
587             }
588             while ( tok.hasMoreTokens() )
589             {
590                 String pluginName = tok.nextToken();
591                 JellyScriptHousing housing = loadHousing( pluginName, manager, pluginDirs, "callbacks" );
592                 if ( housing != null )
593                 {
594                     housings.add( housing );
595                 }
596                 else
597                 {
598                     clearCache();
599                     return false;
600                 }
601             }
602         }
603 
604         for ( Iterator i = dynaTagLibCache.keySet().iterator(); i.hasNext(); )
605         {
606             String uri = (String) i.next();
607             String pluginName = dynaTagLibCache.getProperty( uri );
608             JellyScriptHousing housing = loadHousing( pluginName, manager, pluginDirs, "dynataglib" );
609             if ( housing == null )
610             {
611                 clearCache();
612                 return false;
613             }
614         }
615 
616         // Now map the clean set of plugins
617         for ( Iterator i = preGoals.keySet().iterator(); i.hasNext(); )
618         {
619             String goalName = (String) i.next();
620             List housings = (List) preGoals.get( goalName );
621             for ( Iterator j = housings.iterator(); j.hasNext(); )
622             {
623                 JellyScriptHousing housing = (JellyScriptHousing) j.next();
624                 mapper.addPreGoal( goalName, housing );
625             }
626         }
627         for ( Iterator i = postGoals.keySet().iterator(); i.hasNext(); )
628         {
629             String goalName = (String) i.next();
630             List housings = (List) postGoals.get( goalName );
631             for ( Iterator j = housings.iterator(); j.hasNext(); )
632             {
633                 JellyScriptHousing housing = (JellyScriptHousing) j.next();
634                 mapper.addPostGoal( goalName, housing );
635             }
636         }
637 
638         for ( Iterator i = pluginDynaTagDepsCache.keySet().iterator(); i.hasNext(); )
639         {
640             String pluginName = (String) i.next();
641             JellyScriptHousing housing = manager.loadPluginHousing( pluginName, (File) pluginDirs.get( pluginName ) );
642             String csv = pluginDynaTagDepsCache.getProperty( pluginName );
643             StringTokenizer tok = new StringTokenizer( csv, "," );
644             while ( tok.hasMoreTokens() )
645             {
646                 String uri = tok.nextToken();
647                 mapper.addPluginDynaTagDep( housing, uri );
648             }
649         }
650 
651         for ( Iterator i = pluginCache.keySet().iterator(); i.hasNext(); )
652         {
653             String goalName = (String) i.next();
654             String pluginName = pluginCache.getProperty( goalName );
655             JellyScriptHousing housing = manager.loadPluginHousing( pluginName, (File) pluginDirs.get( pluginName ) );
656             String goal = goalCache.getProperty( goalName );
657             int index = goal.indexOf( ">" );
658             String description = null;
659             if ( index > 0 )
660             {
661                 description = goal.substring( 0, index );
662             }
663             String prereqs = goal.substring( index + 1 );
664 
665             mapper.addGoal( goalName, prereqs, description, housing );
666         }
667 
668         for ( Iterator i = dynaTagLibCache.keySet().iterator(); i.hasNext(); )
669         {
670             String uri = (String) i.next();
671             String pluginName = dynaTagLibCache.getProperty( uri );
672             JellyScriptHousing housing = manager.loadPluginHousing( pluginName, (File) pluginDirs.get( pluginName ) );
673             mapper.addDynaTagLib( uri, housing );
674         }
675 
676         for ( Iterator i = artifactIdCache.keySet().iterator(); i.hasNext(); )
677         {
678             String pluginName = (String) i.next();
679             JellyScriptHousing housing = manager.loadPluginHousing( pluginName, (File) pluginDirs.get( pluginName ) );
680             manager.mapArtifactIdToPluginHousing( artifactIdCache.getProperty( pluginName ), housing );
681         }
682         return true;
683     }
684 
685     /* (non-Javadoc)
686      * @see org.apache.maven.plugin.PluginDefintionHandler#setDefaultGoalName(java.lang.String)
687      */
688     public void setDefaultGoalName( String defaultGoalName )
689     {
690         // this method intentionally left blank
691     }
692 
693     /**
694      * @return Properties
695      */
696     Properties getPluginCache()
697     {
698         return pluginCache;
699     }
700 
701     /**
702      * @return Properties
703      */
704     public Properties getGoalCache()
705     {
706         return goalCache;
707     }
708 
709     /**
710      * @return Returns the callbackCache.
711      */
712     Properties getCallbackCache()
713     {
714         return callbackCache;
715     }
716 
717     /**
718      * @return Returns the dynaTagLibCache.
719      */
720     Properties getDynaTagLibCache()
721     {
722         return dynaTagLibCache;
723     }
724 
725     /**
726      * @return Returns the pluginDynaTagDepsCache.
727      */
728     Properties getPluginDynaTagDepsCache()
729     {
730         return pluginDynaTagDepsCache;
731     }
732 
733 }