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