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