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.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 -> 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 * <define:taglib uri="aptdoc">
103 * <define:jellybean
104 * name="convert"
105 * className="org.apache.maven.aptdoc.AptToXdocConverter"
106 * method="doExecute"
107 * />
108 * </define:taglib>
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
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
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
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
348 prop = prop.substring( tl + 1 );
349 }
350 else if ( i > 0 )
351 {
352
353 prop = prop.substring( 0, i ) + prop.substring( i + tl + 1 );
354 }
355 else
356 {
357
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
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
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
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
686
687
688 public void setDefaultGoalName( String defaultGoalName )
689 {
690
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 }