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