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