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