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