001 /* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019 020 package org.apache.wiki.plugin; 021 022 import org.apache.commons.lang.ClassUtils; 023 import org.apache.log4j.Logger; 024 import org.apache.oro.text.regex.MalformedPatternException; 025 import org.apache.oro.text.regex.MatchResult; 026 import org.apache.oro.text.regex.Pattern; 027 import org.apache.oro.text.regex.PatternCompiler; 028 import org.apache.oro.text.regex.PatternMatcher; 029 import org.apache.oro.text.regex.Perl5Compiler; 030 import org.apache.oro.text.regex.Perl5Matcher; 031 import org.apache.wiki.InternalWikiException; 032 import org.apache.wiki.WikiContext; 033 import org.apache.wiki.WikiEngine; 034 import org.apache.wiki.api.engine.PluginManager; 035 import org.apache.wiki.api.exceptions.PluginException; 036 import org.apache.wiki.api.plugin.InitializablePlugin; 037 import org.apache.wiki.api.plugin.WikiPlugin; 038 import org.apache.wiki.modules.ModuleManager; 039 import org.apache.wiki.modules.WikiModuleInfo; 040 import org.apache.wiki.preferences.Preferences; 041 import org.apache.wiki.util.ClassUtil; 042 import org.apache.wiki.util.FileUtil; 043 import org.apache.wiki.util.TextUtil; 044 import org.apache.wiki.util.XHTML; 045 import org.apache.wiki.util.XhtmlUtil; 046 import org.apache.wiki.util.XmlUtil; 047 import org.jdom2.Element; 048 049 import java.io.IOException; 050 import java.io.PrintWriter; 051 import java.io.StreamTokenizer; 052 import java.io.StringReader; 053 import java.io.StringWriter; 054 import java.text.MessageFormat; 055 import java.util.ArrayList; 056 import java.util.Collection; 057 import java.util.HashMap; 058 import java.util.Iterator; 059 import java.util.List; 060 import java.util.Map; 061 import java.util.NoSuchElementException; 062 import java.util.Properties; 063 import java.util.ResourceBundle; 064 import java.util.Set; 065 import java.util.StringTokenizer; 066 import java.util.TreeSet; 067 068 /** 069 * Manages plugin classes. There exists a single instance of PluginManager 070 * per each instance of WikiEngine, that is, each JSPWiki instance. 071 * <P> 072 * A plugin is defined to have three parts: 073 * <OL> 074 * <li>The plugin class 075 * <li>The plugin parameters 076 * <li>The plugin body 077 * </ol> 078 * 079 * For example, in the following line of code: 080 * <pre> 081 * [{INSERT org.apache.wiki.plugin.FunnyPlugin foo='bar' 082 * blob='goo' 083 * 084 * abcdefghijklmnopqrstuvw 085 * 01234567890}] 086 * </pre> 087 * 088 * The plugin class is "org.apache.wiki.plugin.FunnyPlugin", the 089 * parameters are "foo" and "blob" (having values "bar" and "goo", 090 * respectively), and the plugin body is then 091 * "abcdefghijklmnopqrstuvw\n01234567890". The plugin body is 092 * accessible via a special parameter called "_body". 093 * <p> 094 * If the parameter "debug" is set to "true" for the plugin, 095 * JSPWiki will output debugging information directly to the page if there 096 * is an exception. 097 * <P> 098 * The class name can be shortened, and marked without the package. 099 * For example, "FunnyPlugin" would be expanded to 100 * "org.apache.wiki.plugin.FunnyPlugin" automatically. It is also 101 * possible to define other packages, by setting the 102 * "jspwiki.plugin.searchPath" property. See the included 103 * jspwiki.properties file for examples. 104 * <P> 105 * Even though the nominal way of writing the plugin is 106 * <pre> 107 * [{INSERT pluginclass WHERE param1=value1...}], 108 * </pre> 109 * it is possible to shorten this quite a lot, by skipping the 110 * INSERT, and WHERE words, and dropping the package name. For 111 * example: 112 * 113 * <pre> 114 * [{INSERT org.apache.wiki.plugin.Counter WHERE name='foo'}] 115 * </pre> 116 * 117 * is the same as 118 * <pre> 119 * [{Counter name='foo'}] 120 * </pre> 121 * <h3>Plugin property files</h3> 122 * <p> 123 * Since 2.3.25 you can also define a generic plugin XML properties file per 124 * each JAR file. 125 * <pre> 126 * <modules> 127 * <plugin class="org.apache.wiki.foo.TestPlugin"> 128 * <author>Janne Jalkanen</author> 129 * <script>foo.js</script> 130 * <stylesheet>foo.css</stylesheet> 131 * <alias>code</alias> 132 * </plugin> 133 * <plugin class="org.apache.wiki.foo.TestPlugin2"> 134 * <author>Janne Jalkanen</author> 135 * </plugin> 136 * </modules> 137 * </pre> 138 * <h3>Plugin lifecycle</h3> 139 * 140 * <p>Plugin can implement multiple interfaces to let JSPWiki know at which stages they should 141 * be invoked: 142 * <ul> 143 * <li>InitializablePlugin: If your plugin implements this interface, the initialize()-method is 144 * called once for this class 145 * before any actual execute() methods are called. You should use the initialize() for e.g. 146 * precalculating things. But notice that this method is really called only once during the 147 * entire WikiEngine lifetime. The InitializablePlugin is available from 2.5.30 onwards.</li> 148 * <li>ParserStagePlugin: If you implement this interface, the executeParse() method is called 149 * when JSPWiki is forming the DOM tree. You will receive an incomplete DOM tree, as well 150 * as the regular parameters. However, since JSPWiki caches the DOM tree to speed up later 151 * places, which means that whatever this method returns would be irrelevant. You can do some DOM 152 * tree manipulation, though. The ParserStagePlugin is available from 2.5.30 onwards.</li> 153 * <li>WikiPlugin: The regular kind of plugin which is executed at every rendering stage. Each 154 * new page load is guaranteed to invoke the plugin, unlike with the ParserStagePlugins.</li> 155 * </ul> 156 * 157 * @since 1.6.1 158 */ 159 public class DefaultPluginManager extends ModuleManager implements PluginManager { 160 161 private static final String PLUGIN_INSERT_PATTERN = "\\{?(INSERT)?\\s*([\\w\\._]+)[ \\t]*(WHERE)?[ \\t]*"; 162 163 private static Logger log = Logger.getLogger( DefaultPluginManager.class ); 164 165 private static final String DEFAULT_FORMS_PACKAGE = "org.apache.wiki.forms"; 166 167 private ArrayList<String> m_searchPath = new ArrayList<String>(); 168 169 private ArrayList<String> m_externalJars = new ArrayList<String>(); 170 171 private Pattern m_pluginPattern; 172 173 private boolean m_pluginsEnabled = true; 174 175 /** 176 * Keeps a list of all known plugin classes. 177 */ 178 private Map<String, WikiPluginInfo> m_pluginClassMap = new HashMap<String, WikiPluginInfo>(); 179 180 /** 181 * Create a new PluginManager. 182 * 183 * @param engine WikiEngine which owns this manager. 184 * @param props Contents of a "jspwiki.properties" file. 185 */ 186 public DefaultPluginManager( WikiEngine engine, Properties props ) { 187 super( engine ); 188 String packageNames = props.getProperty( PROP_SEARCHPATH ); 189 190 if ( packageNames != null ) { 191 StringTokenizer tok = new StringTokenizer( packageNames, "," ); 192 193 while( tok.hasMoreTokens() ) { 194 m_searchPath.add( tok.nextToken().trim() ); 195 } 196 } 197 198 String externalJars = props.getProperty( PROP_EXTERNALJARS ); 199 200 if( externalJars != null ) { 201 StringTokenizer tok = new StringTokenizer( externalJars, "," ); 202 203 while( tok.hasMoreTokens() ) { 204 m_externalJars.add( tok.nextToken().trim() ); 205 } 206 } 207 208 registerPlugins(); 209 210 // 211 // The default packages are always added. 212 // 213 m_searchPath.add( DEFAULT_PACKAGE ); 214 m_searchPath.add( DEFAULT_FORMS_PACKAGE ); 215 216 PatternCompiler compiler = new Perl5Compiler(); 217 218 try { 219 m_pluginPattern = compiler.compile( PLUGIN_INSERT_PATTERN ); 220 } catch( MalformedPatternException e ) { 221 log.fatal( "Internal error: someone messed with pluginmanager patterns.", e ); 222 throw new InternalWikiException( "PluginManager patterns are broken" ); 223 } 224 225 } 226 227 /** 228 * {@inheritDoc} 229 */ 230 public void enablePlugins( boolean enabled ) { 231 m_pluginsEnabled = enabled; 232 } 233 234 /** 235 * {@inheritDoc} 236 */ 237 public boolean pluginsEnabled() { 238 return m_pluginsEnabled; 239 } 240 241 /** 242 * {@inheritDoc} 243 */ 244 public Pattern getPluginPattern() { 245 return m_pluginPattern; 246 } 247 248 /** 249 * {@inheritDoc} 250 */ 251 public String getPluginSearchPath() { 252 return TextUtil.getStringProperty( m_engine.getWikiProperties(), PROP_SEARCHPATH, null ); 253 } 254 255 /** 256 * Attempts to locate a plugin class from the class path set in the property file. 257 * 258 * @param classname Either a fully fledged class name, or just the name of the file (that is, 259 * "org.apache.wiki.plugin.Counter" or just plain "Counter"). 260 * 261 * @return A found class. 262 * 263 * @throws ClassNotFoundException if no such class exists. 264 */ 265 private Class< ? > findPluginClass( String classname ) throws ClassNotFoundException { 266 return ClassUtil.findClass( m_searchPath, m_externalJars, classname ); 267 } 268 269 /** 270 * Outputs a HTML-formatted version of a stack trace. 271 */ 272 private String stackTrace( Map<String,String> params, Throwable t ) 273 { 274 Element div = XhtmlUtil.element(XHTML.div,"Plugin execution failed, stack trace follows:"); 275 div.setAttribute(XHTML.ATTR_class,"debug"); 276 277 278 StringWriter out = new StringWriter(); 279 t.printStackTrace(new PrintWriter(out)); 280 div.addContent(XhtmlUtil.element(XHTML.pre,out.toString())); 281 div.addContent(XhtmlUtil.element(XHTML.b,"Parameters to the plugin")); 282 283 Element list = XhtmlUtil.element(XHTML.ul); 284 285 for( Iterator<Map.Entry<String,String>> i = params.entrySet().iterator(); i.hasNext(); ) { 286 Map.Entry<String,String> e = i.next(); 287 String key = e.getKey(); 288 list.addContent(XhtmlUtil.element(XHTML.li,key + "'='" + e.getValue())); 289 } 290 291 div.addContent(list); 292 293 return XhtmlUtil.serialize(div); 294 } 295 296 /** 297 * Executes a plugin class in the given context. 298 * <P>Used to be private, but is public since 1.9.21. 299 * 300 * @param context The current WikiContext. 301 * @param classname The name of the class. Can also be a 302 * shortened version without the package name, since the class name is searched from the 303 * package search path. 304 * 305 * @param params A parsed map of key-value pairs. 306 * 307 * @return Whatever the plugin returns. 308 * 309 * @throws PluginException If the plugin execution failed for 310 * some reason. 311 * 312 * @since 2.0 313 */ 314 public String execute( WikiContext context, String classname, Map< String, String > params ) throws PluginException { 315 if( !m_pluginsEnabled ) { 316 return ""; 317 } 318 319 ResourceBundle rb = Preferences.getBundle( context, WikiPlugin.CORE_PLUGINS_RESOURCEBUNDLE ); 320 boolean debug = TextUtil.isPositive( params.get( PARAM_DEBUG ) ); 321 try { 322 // 323 // Create... 324 // 325 WikiPlugin plugin = newWikiPlugin( classname, rb ); 326 if( plugin == null ) { 327 return "Plugin '" + classname + "' not compatible with this version of JSPWiki"; 328 } 329 330 // 331 // ...and launch. 332 // 333 try { 334 return plugin.execute( context, params ); 335 } catch( PluginException e ) { 336 if( debug ) { 337 return stackTrace( params, e ); 338 } 339 340 // Just pass this exception onward. 341 throw ( PluginException )e.fillInStackTrace(); 342 } catch( Throwable t ) { 343 // But all others get captured here. 344 log.info( "Plugin failed while executing:", t ); 345 if( debug ) { 346 return stackTrace( params, t ); 347 } 348 349 throw new PluginException( rb.getString( "plugin.error.failed" ), t ); 350 } 351 352 } catch( ClassCastException e ) { 353 throw new PluginException( MessageFormat.format( rb.getString( "plugin.error.notawikiplugin" ), classname ), e ); 354 } 355 } 356 357 /** 358 * Parses plugin arguments. Handles quotes and all other kewl stuff. 359 * 360 * <h3>Special parameters</h3> 361 * The plugin body is put into a special parameter defined by {@link #PARAM_BODY}; 362 * the plugin's command line into a parameter defined by {@link #PARAM_CMDLINE}; 363 * and the bounds of the plugin within the wiki page text by a parameter defined 364 * by {@link #PARAM_BOUNDS}, whose value is stored as a two-element int[] array, 365 * i.e., <tt>[start,end]</tt>. 366 * 367 * @param argstring The argument string to the plugin. This is 368 * typically a list of key-value pairs, using "'" to escape 369 * spaces in strings, followed by an empty line and then the 370 * plugin body. In case the parameter is null, will return an 371 * empty parameter list. 372 * 373 * @return A parsed list of parameters. 374 * 375 * @throws IOException If the parsing fails. 376 */ 377 public Map< String, String > parseArgs( String argstring ) throws IOException { 378 Map< String, String > arglist = new HashMap< String, String >(); 379 380 // 381 // Protection against funny users. 382 // 383 if( argstring == null ) return arglist; 384 385 arglist.put( PARAM_CMDLINE, argstring ); 386 387 StringReader in = new StringReader(argstring); 388 StreamTokenizer tok = new StreamTokenizer(in); 389 int type; 390 391 392 String param = null; 393 String value = null; 394 395 tok.eolIsSignificant( true ); 396 397 boolean potentialEmptyLine = false; 398 boolean quit = false; 399 400 while( !quit ) { 401 String s; 402 type = tok.nextToken(); 403 404 switch( type ) { 405 case StreamTokenizer.TT_EOF: 406 quit = true; 407 s = null; 408 break; 409 410 case StreamTokenizer.TT_WORD: 411 s = tok.sval; 412 potentialEmptyLine = false; 413 break; 414 415 case StreamTokenizer.TT_EOL: 416 quit = potentialEmptyLine; 417 potentialEmptyLine = true; 418 s = null; 419 break; 420 421 case StreamTokenizer.TT_NUMBER: 422 s = Integer.toString( (int) tok.nval ); 423 potentialEmptyLine = false; 424 break; 425 426 case '\'': 427 s = tok.sval; 428 break; 429 430 default: 431 s = null; 432 } 433 434 // 435 // Assume that alternate words on the line are 436 // parameter and value, respectively. 437 // 438 if( s != null ) { 439 if( param == null ) { 440 param = s; 441 } else { 442 value = s; 443 444 arglist.put( param, value ); 445 446 // log.debug("ARG: "+param+"="+value); 447 param = null; 448 } 449 } 450 } 451 452 // 453 // Now, we'll check the body. 454 // 455 if( potentialEmptyLine ) { 456 StringWriter out = new StringWriter(); 457 FileUtil.copyContents( in, out ); 458 459 String bodyContent = out.toString(); 460 461 if( bodyContent != null ) { 462 arglist.put( PARAM_BODY, bodyContent ); 463 } 464 } 465 466 return arglist; 467 } 468 469 /** 470 * Parses a plugin. Plugin commands are of the form: 471 * [{INSERT myplugin WHERE param1=value1, param2=value2}] 472 * myplugin may either be a class name or a plugin alias. 473 * <P> 474 * This is the main entry point that is used. 475 * 476 * @param context The current WikiContext. 477 * @param commandline The full command line, including plugin name, parameters and body. 478 * 479 * @return HTML as returned by the plugin, or possibly an error message. 480 * 481 * @throws PluginException From the plugin itself, it propagates, waah! 482 */ 483 public String execute( WikiContext context, String commandline ) throws PluginException { 484 if( !m_pluginsEnabled ) { 485 return ""; 486 } 487 488 ResourceBundle rb = Preferences.getBundle( context, WikiPlugin.CORE_PLUGINS_RESOURCEBUNDLE ); 489 PatternMatcher matcher = new Perl5Matcher(); 490 491 try { 492 if( matcher.contains( commandline, m_pluginPattern ) ) { 493 MatchResult res = matcher.getMatch(); 494 495 String plugin = res.group(2); 496 String args = commandline.substring(res.endOffset(0), 497 commandline.length() - 498 (commandline.charAt(commandline.length()-1) == '}' ? 1 : 0 ) ); 499 Map<String, String> arglist = parseArgs( args ); 500 501 return execute( context, plugin, arglist ); 502 } 503 } catch( NoSuchElementException e ) { 504 String msg = "Missing parameter in plugin definition: "+commandline; 505 log.warn( msg, e ); 506 throw new PluginException( MessageFormat.format( rb.getString( "plugin.error.missingparameter" ), commandline ) ); 507 } catch( IOException e ) { 508 String msg = "Zyrf. Problems with parsing arguments: "+commandline; 509 log.warn( msg, e ); 510 throw new PluginException( MessageFormat.format( rb.getString( "plugin.error.parsingarguments" ), commandline ) ); 511 } 512 513 // FIXME: We could either return an empty string "", or 514 // the original line. If we want unsuccessful requests 515 // to be invisible, then we should return an empty string. 516 return commandline; 517 } 518 519 /** 520 * Register a plugin. 521 */ 522 private void registerPlugin( WikiPluginInfo pluginClass ) { 523 String name; 524 525 // Registrar the plugin with the className without the package-part 526 name = pluginClass.getName(); 527 if( name != null ) { 528 log.debug( "Registering plugin [name]: " + name ); 529 m_pluginClassMap.put( name, pluginClass ); 530 } 531 532 // Registrar the plugin with a short convenient name. 533 name = pluginClass.getAlias(); 534 if( name != null ) { 535 log.debug( "Registering plugin [shortName]: " + name ); 536 m_pluginClassMap.put( name, pluginClass ); 537 } 538 539 // Registrar the plugin with the className with the package-part 540 name = pluginClass.getClassName(); 541 if( name != null ) { 542 log.debug( "Registering plugin [className]: " + name ); 543 m_pluginClassMap.put( name, pluginClass ); 544 } 545 546 pluginClass.initializePlugin( m_engine , m_searchPath, m_externalJars); 547 } 548 549 private void registerPlugins() { 550 log.info( "Registering plugins" ); 551 List< Element > plugins = XmlUtil.parse( PLUGIN_RESOURCE_LOCATION, "/modules/plugin" ); 552 553 // 554 // Register all plugins which have created a resource containing its properties. 555 // 556 // Get all resources of all plugins. 557 // 558 for( Element pluginEl : plugins ) { 559 String className = pluginEl.getAttributeValue( "class" ); 560 WikiPluginInfo pluginInfo = WikiPluginInfo.newInstance( className, pluginEl ,m_searchPath, m_externalJars); 561 562 if( pluginInfo != null ) { 563 registerPlugin( pluginInfo ); 564 } 565 } 566 } 567 568 /** 569 * Contains information about a bunch of plugins. 570 * 571 * 572 */ 573 // FIXME: This class needs a better interface to return all sorts of possible 574 // information from the plugin XML. In fact, it probably should have 575 // some sort of a superclass system. 576 public static final class WikiPluginInfo extends WikiModuleInfo { 577 578 private String m_className; 579 private String m_alias; 580 private Class<?> m_clazz; 581 582 private boolean m_initialized = false; 583 584 /** 585 * Creates a new plugin info object which can be used to access a plugin. 586 * 587 * @param className Either a fully qualified class name, or a "short" name which is then 588 * checked against the internal list of plugin packages. 589 * @param el A JDOM Element containing the information about this class. 590 * @param searchPath A List of Strings, containing different package names. 591 * @param externalJars the list of external jars to search 592 * @return A WikiPluginInfo object. 593 */ 594 protected static WikiPluginInfo newInstance( String className, Element el, List<String> searchPath, List<String> externalJars ) { 595 if( className == null || className.length() == 0 ) return null; 596 597 WikiPluginInfo info = new WikiPluginInfo( className ); 598 info.initializeFromXML( el ); 599 return info; 600 } 601 602 /** 603 * Initializes a plugin, if it has not yet been initialized. 604 * 605 * @param engine The WikiEngine 606 * @param searchPath A List of Strings, containing different package names. 607 * @param externalJars the list of external jars to search 608 */ 609 protected void initializePlugin( WikiEngine engine , List<String> searchPath, List<String> externalJars) { 610 if( !m_initialized ) { 611 // This makes sure we only try once per class, even if init fails. 612 m_initialized = true; 613 614 try { 615 WikiPlugin p = newPluginInstance(searchPath, externalJars); 616 if( p instanceof InitializablePlugin ) { 617 ( ( InitializablePlugin )p ).initialize( engine ); 618 } 619 } catch( Exception e ) { 620 log.info( "Cannot initialize plugin " + m_className, e ); 621 } 622 } 623 } 624 625 /** 626 * {@inheritDoc} 627 */ 628 @Override 629 protected void initializeFromXML( Element el ) { 630 super.initializeFromXML( el ); 631 m_alias = el.getChildText( "alias" ); 632 } 633 634 /** 635 * Create a new WikiPluginInfo based on the Class information. 636 * 637 * @param clazz The class to check 638 * @return A WikiPluginInfo instance 639 */ 640 protected static WikiPluginInfo newInstance( Class< ? > clazz ) { 641 return new WikiPluginInfo( clazz.getName() ); 642 } 643 644 private WikiPluginInfo( String className ) { 645 super( className ); 646 setClassName( className ); 647 } 648 649 private void setClassName( String fullClassName ) { 650 m_name = ClassUtils.getShortClassName( fullClassName ); 651 m_className = fullClassName; 652 } 653 654 /** 655 * Returns the full class name of this object. 656 * @return The full class name of the object. 657 */ 658 public String getClassName() { 659 return m_className; 660 } 661 662 /** 663 * Returns the alias name for this object. 664 * @return An alias name for the plugin. 665 */ 666 public String getAlias() { 667 return m_alias; 668 } 669 670 /** 671 * Creates a new plugin instance. 672 * 673 * @param searchPath A List of Strings, containing different package names. 674 * @param externalJars the list of external jars to search 675 676 * @return A new plugin. 677 * @throws ClassNotFoundException If the class declared was not found. 678 * @throws InstantiationException If the class cannot be instantiated- 679 * @throws IllegalAccessException If the class cannot be accessed. 680 */ 681 682 public WikiPlugin newPluginInstance(List<String> searchPath, List<String> externalJars) throws ClassNotFoundException, InstantiationException, IllegalAccessException { 683 if( m_clazz == null ) { 684 m_clazz = ClassUtil.findClass(searchPath, externalJars ,m_className); 685 } 686 687 return (WikiPlugin) m_clazz.newInstance(); 688 } 689 690 /** 691 * Returns a text for IncludeResources. 692 * 693 * @param type Either "script" or "stylesheet" 694 * @return Text, or an empty string, if there is nothing to be included. 695 */ 696 public String getIncludeText( String type ) { 697 try { 698 if( "script".equals( type ) ) { 699 return getScriptText(); 700 } else if( "stylesheet".equals( type ) ) { 701 return getStylesheetText(); 702 } 703 } catch( Exception ex ) { 704 // We want to fail gracefully here 705 return ex.getMessage(); 706 } 707 708 return null; 709 } 710 711 private String getScriptText() throws IOException { 712 if( m_scriptText != null ) { 713 return m_scriptText; 714 } 715 716 if( m_scriptLocation == null ) { 717 return ""; 718 } 719 720 try { 721 m_scriptText = getTextResource(m_scriptLocation); 722 } catch( IOException ex ) { 723 // Only throw this exception once! 724 m_scriptText = ""; 725 throw ex; 726 } 727 728 return m_scriptText; 729 } 730 731 private String getStylesheetText() throws IOException { 732 if( m_stylesheetText != null ) { 733 return m_stylesheetText; 734 } 735 736 if( m_stylesheetLocation == null ) { 737 return ""; 738 } 739 740 try { 741 m_stylesheetText = getTextResource(m_stylesheetLocation); 742 } catch( IOException ex ) { 743 // Only throw this exception once! 744 m_stylesheetText = ""; 745 throw ex; 746 } 747 748 return m_stylesheetText; 749 } 750 751 /** 752 * Returns a string suitable for debugging. Don't assume that the format would stay the same. 753 * 754 * @return Something human-readable 755 */ 756 public String toString() { 757 return "Plugin :[name=" + m_name + "][className=" + m_className + "]"; 758 } 759 } // WikiPluginClass 760 761 /** 762 * {@inheritDoc} 763 */ 764 public Collection< WikiModuleInfo > modules() { 765 Set< WikiModuleInfo > ls = new TreeSet< WikiModuleInfo >(); 766 767 for( Iterator< WikiPluginInfo > i = m_pluginClassMap.values().iterator(); i.hasNext(); ) { 768 WikiModuleInfo wmi = i.next(); 769 if( !ls.contains( wmi ) ) ls.add( wmi ); 770 } 771 772 return ls; 773 } 774 775 /** 776 * Creates a {@link WikiPlugin}. 777 * 778 * @param pluginName plugin's classname 779 * @param rb {@link ResourceBundle} with i18ned text for exceptions. 780 * @return a {@link WikiPlugin}. 781 * @throws PluginException if there is a problem building the {@link WikiPlugin}. 782 */ 783 public WikiPlugin newWikiPlugin( String pluginName, ResourceBundle rb ) throws PluginException { 784 WikiPlugin plugin = null; 785 WikiPluginInfo pluginInfo = m_pluginClassMap.get( pluginName ); 786 try { 787 if( pluginInfo == null ) { 788 pluginInfo = WikiPluginInfo.newInstance( findPluginClass( pluginName ) ); 789 registerPlugin( pluginInfo ); 790 } 791 792 if( !checkCompatibility( pluginInfo ) ) { 793 String msg = "Plugin '" + pluginInfo.getName() + "' not compatible with this version of JSPWiki"; 794 log.info( msg ); 795 } else { 796 plugin = pluginInfo.newPluginInstance(m_searchPath, m_externalJars); 797 } 798 } catch( ClassNotFoundException e ) { 799 throw new PluginException( MessageFormat.format( rb.getString( "plugin.error.couldnotfind" ), pluginName ), e ); 800 } catch( InstantiationException e ) { 801 throw new PluginException( MessageFormat.format( rb.getString( "plugin.error.cannotinstantiate" ), pluginName ), e ); 802 } catch( IllegalAccessException e ) { 803 throw new PluginException( MessageFormat.format( rb.getString( "plugin.error.notallowed" ), pluginName ), e ); 804 } catch( Exception e ) { 805 throw new PluginException( MessageFormat.format( rb.getString( "plugin.error.instantationfailed" ), pluginName ), e ); 806 } 807 return plugin; 808 } 809 810 }