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