001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001 Janne Jalkanen (Janne.Jalkanen@iki.fi)
005:
006: This program is free software; you can redistribute it and/or modify
007: it under the terms of the GNU Lesser General Public License as published by
008: the Free Software Foundation; either version 2.1 of the License, or
009: (at your option) any later version.
010:
011: This program is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public License
017: along with this program; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package com.ecyrd.jspwiki.plugin;
021:
022: import java.io.*;
023: import java.net.URL;
024: import java.text.MessageFormat;
025: import java.util.*;
026:
027: import org.apache.commons.lang.ClassUtils;
028: import org.apache.ecs.xhtml.*;
029: import org.apache.log4j.Logger;
030: import org.apache.oro.text.regex.*;
031: import org.jdom.Document;
032: import org.jdom.Element;
033: import org.jdom.JDOMException;
034: import org.jdom.input.SAXBuilder;
035: import org.jdom.xpath.XPath;
036:
037: import com.ecyrd.jspwiki.*;
038: import com.ecyrd.jspwiki.modules.ModuleManager;
039: import com.ecyrd.jspwiki.modules.WikiModuleInfo;
040: import com.ecyrd.jspwiki.parser.PluginContent;
041: import com.ecyrd.jspwiki.util.ClassUtil;
042:
043: /**
044: * Manages plugin classes. There exists a single instance of PluginManager
045: * per each instance of WikiEngine, that is, each JSPWiki instance.
046: * <P>
047: * A plugin is defined to have three parts:
048: * <OL>
049: * <li>The plugin class
050: * <li>The plugin parameters
051: * <li>The plugin body
052: * </ol>
053: *
054: * For example, in the following line of code:
055: * <pre>
056: * [{INSERT com.ecyrd.jspwiki.plugin.FunnyPlugin foo='bar'
057: * blob='goo'
058: *
059: * abcdefghijklmnopqrstuvw
060: * 01234567890}]
061: * </pre>
062: *
063: * The plugin class is "com.ecyrd.jspwiki.plugin.FunnyPlugin", the
064: * parameters are "foo" and "blob" (having values "bar" and "goo",
065: * respectively), and the plugin body is then
066: * "abcdefghijklmnopqrstuvw\n01234567890". The plugin body is
067: * accessible via a special parameter called "_body".
068: * <p>
069: * If the parameter "debug" is set to "true" for the plugin,
070: * JSPWiki will output debugging information directly to the page if there
071: * is an exception.
072: * <P>
073: * The class name can be shortened, and marked without the package.
074: * For example, "FunnyPlugin" would be expanded to
075: * "com.ecyrd.jspwiki.plugin.FunnyPlugin" automatically. It is also
076: * possible to defined other packages, by setting the
077: * "jspwiki.plugin.searchPath" property. See the included
078: * jspwiki.properties file for examples.
079: * <P>
080: * Even though the nominal way of writing the plugin is
081: * <pre>
082: * [{INSERT pluginclass WHERE param1=value1...}],
083: * </pre>
084: * it is possible to shorten this quite a lot, by skipping the
085: * INSERT, and WHERE words, and dropping the package name. For
086: * example:
087: *
088: * <pre>
089: * [{INSERT com.ecyrd.jspwiki.plugin.Counter WHERE name='foo'}]
090: * </pre>
091: *
092: * is the same as
093: * <pre>
094: * [{Counter name='foo'}]
095: * </pre>
096: * <h3>Plugin property files</h3>
097: * <p>
098: * Since 2.3.25 you can also define a generic plugin XML properties file per
099: * each JAR file.
100: * <pre>
101: * <modules>
102: * <plugin class="com.ecyrd.jspwiki.foo.TestPlugin">
103: * <author>Janne Jalkanen</author>
104: * <script>foo.js</script>
105: * <stylesheet>foo.css</stylesheet>
106: * <alias>code</alias>
107: * </plugin>
108: * <plugin class="com.ecyrd.jspwiki.foo.TestPlugin2">
109: * <author>Janne Jalkanen</author>
110: * </plugin>
111: * </modules>
112: * </pre>
113: * <h3>Plugin lifecycle</h3>
114: *
115: * <p>Plugin can implement multiple interfaces to let JSPWiki know at which stages they should
116: * be invoked:
117: * <ul>
118: * <li>InitializablePlugin: If your plugin implements this interface, the initialize()-method is
119: * called once for this class
120: * before any actual execute() methods are called. You should use the initialize() for e.g.
121: * precalculating things. But notice that this method is really called only once during the
122: * entire WikiEngine lifetime. The InitializablePlugin is available from 2.5.30 onwards.</li>
123: * <li>ParserStagePlugin: If you implement this interface, the executeParse() method is called
124: * when JSPWiki is forming the DOM tree. You will receive an incomplete DOM tree, as well
125: * as the regular parameters. However, since JSPWiki caches the DOM tree to speed up later
126: * places, which means that whatever this method returns would be irrelevant. You can do some DOM
127: * tree manipulation, though. The ParserStagePlugin is available from 2.5.30 onwards.</li>
128: * <li>WikiPlugin: The regular kind of plugin which is executed at every rendering stage. Each
129: * new page load is guaranteed to invoke the plugin, unlike with the ParserStagePlugins.</li>
130: * </ul>
131: *
132: * @author Janne Jalkanen
133: * @since 1.6.1
134: */
135: public class PluginManager extends ModuleManager {
136: private static final String PLUGIN_INSERT_PATTERN = "\\{?(INSERT)?\\s*([\\w\\._]+)[ \\t]*(WHERE)?[ \\t]*";
137:
138: private static Logger log = Logger.getLogger(PluginManager.class);
139:
140: /**
141: * This is the default package to try in case the instantiation
142: * fails.
143: */
144: public static final String DEFAULT_PACKAGE = "com.ecyrd.jspwiki.plugin";
145:
146: public static final String DEFAULT_FORMS_PACKAGE = "com.ecyrd.jspwiki.forms";
147:
148: /**
149: * The property name defining which packages will be searched for properties.
150: */
151: public static final String PROP_SEARCHPATH = "jspwiki.plugin.searchPath";
152:
153: /**
154: * The name of the body content. Current value is "_body".
155: */
156: public static final String PARAM_BODY = "_body";
157:
158: /**
159: * The name of the command line content parameter. The value is "_cmdline".
160: */
161: public static final String PARAM_CMDLINE = "_cmdline";
162:
163: /**
164: * The name of the parameter containing the start and end positions in the
165: * read stream of the plugin text (stored as a two-element int[], start
166: * and end resp.).
167: */
168: public static final String PARAM_BOUNDS = "_bounds";
169:
170: /**
171: * A special name to be used in case you want to see debug output
172: */
173: public static final String PARAM_DEBUG = "debug";
174:
175: private ArrayList m_searchPath = new ArrayList();
176:
177: private Pattern m_pluginPattern;
178:
179: private boolean m_pluginsEnabled = true;
180:
181: /**
182: * Keeps a list of all known plugin classes.
183: */
184: private Map m_pluginClassMap = new HashMap();
185:
186: /**
187: * Create a new PluginManager.
188: *
189: * @param props Contents of a "jspwiki.properties" file.
190: */
191: public PluginManager(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: registerPlugins();
204:
205: //
206: // The default packages are always added.
207: //
208: m_searchPath.add(DEFAULT_PACKAGE);
209: m_searchPath.add(DEFAULT_FORMS_PACKAGE);
210:
211: PatternCompiler compiler = new Perl5Compiler();
212:
213: try {
214: m_pluginPattern = compiler.compile(PLUGIN_INSERT_PATTERN);
215: } catch (MalformedPatternException e) {
216: log
217: .fatal(
218: "Internal error: someone messed with pluginmanager patterns.",
219: e);
220: throw new InternalWikiException(
221: "PluginManager patterns are broken");
222: }
223:
224: }
225:
226: /**
227: * Enables or disables plugin execution.
228: */
229: public void enablePlugins(boolean enabled) {
230: m_pluginsEnabled = enabled;
231: }
232:
233: /**
234: * Returns plugin execution status. If false, plugins are not
235: * executed when they are encountered on a WikiPage, and an
236: * empty string is returned in their place.
237: */
238: public boolean pluginsEnabled() {
239: return m_pluginsEnabled;
240: }
241:
242: /**
243: * Returns true if the link is really command to insert
244: * a plugin.
245: * <P>
246: * Currently we just check if the link starts with "{INSERT",
247: * or just plain "{" but not "{$".
248: *
249: * @param link Link text, i.e. the contents of text between [].
250: * @return True, if this link seems to be a command to insert a plugin here.
251: */
252: public static boolean isPluginLink(String link) {
253: return link.startsWith("{INSERT")
254: || (link.startsWith("{") && !link.startsWith("{$"));
255: }
256:
257: /**
258: * Attempts to locate a plugin class from the class path
259: * set in the property file.
260: *
261: * @param classname Either a fully fledged class name, or just
262: * the name of the file (that is,
263: * "com.ecyrd.jspwiki.plugin.Counter" or just plain "Counter").
264: *
265: * @return A found class.
266: *
267: * @throws ClassNotFoundException if no such class exists.
268: */
269: private Class findPluginClass(String classname)
270: throws ClassNotFoundException {
271: return ClassUtil.findClass(m_searchPath, classname);
272: }
273:
274: /**
275: * Outputs a HTML-formatted version of a stack trace.
276: */
277: private String stackTrace(Map params, Throwable t) {
278: div d = new div();
279: d.setClass("debug");
280: d.addElement("Plugin execution failed, stack trace follows:");
281: StringWriter out = new StringWriter();
282: t.printStackTrace(new PrintWriter(out));
283: d.addElement(new pre(out.toString()));
284: d.addElement(new b("Parameters to the plugin"));
285:
286: ul list = new ul();
287: for (Iterator i = params.entrySet().iterator(); i.hasNext();) {
288: Map.Entry e = (Map.Entry) i.next();
289: String key = (String) e.getKey();
290:
291: list.addElement(new li(key + "'='" + e.getValue()));
292: }
293:
294: d.addElement(list);
295:
296: return d.toString();
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,
318: Map params) throws PluginException {
319: if (!m_pluginsEnabled)
320: return "";
321:
322: ResourceBundle rb = context
323: .getBundle(WikiPlugin.CORE_PLUGINS_RESOURCEBUNDLE);
324: Object[] args = { classname };
325: try {
326: WikiPlugin plugin;
327:
328: boolean debug = TextUtil.isPositive((String) params
329: .get(PARAM_DEBUG));
330:
331: WikiPluginInfo pluginInfo = (WikiPluginInfo) m_pluginClassMap
332: .get(classname);
333:
334: if (pluginInfo == null) {
335: pluginInfo = WikiPluginInfo
336: .newInstance(findPluginClass(classname));
337: registerPlugin(pluginInfo);
338: }
339:
340: if (!checkCompatibility(pluginInfo)) {
341: String msg = "Plugin '"
342: + pluginInfo.getName()
343: + "' not compatible with this version of JSPWiki";
344: log.info(msg);
345: return msg;
346: }
347:
348: //
349: // Create...
350: //
351: try {
352: plugin = pluginInfo.newPluginInstance();
353: } catch (InstantiationException e) {
354: throw new PluginException(MessageFormat.format(rb
355: .getString("plugin.error.cannotinstantiate"),
356: args), e);
357: } catch (IllegalAccessException e) {
358: throw new PluginException(MessageFormat.format(rb
359: .getString("plugin.error.notallowed"), args), e);
360: } catch (Exception e) {
361: throw new PluginException(MessageFormat.format(rb
362: .getString("plugin.error.instantationfailed"),
363: args), e);
364: }
365:
366: //
367: // ...and launch.
368: //
369: try {
370: return plugin.execute(context, params);
371: } catch (PluginException e) {
372: if (debug) {
373: return stackTrace(params, e);
374: }
375:
376: // Just pass this exception onward.
377: throw (PluginException) e.fillInStackTrace();
378: } catch (Throwable t) {
379: // But all others get captured here.
380: log.info("Plugin failed while executing:", t);
381: if (debug) {
382: return stackTrace(params, t);
383: }
384:
385: throw new PluginException(rb
386: .getString("plugin.error.pluginfailed"), t);
387: }
388:
389: } catch (ClassNotFoundException e) {
390: throw new PluginException(MessageFormat.format(rb
391: .getString("plugin.error.couldnotfind"), args), e);
392: } catch (ClassCastException e) {
393: throw new PluginException(MessageFormat.format(rb
394: .getString("plugin.error.notawikiplugin"), args), e);
395: }
396: }
397:
398: /**
399: * Parses plugin arguments. Handles quotes and all other kewl stuff.
400: *
401: * <h3>Special parameters</h3>
402: * The plugin body is put into a special parameter defined by {@link #PARAM_BODY};
403: * the plugin's command line into a parameter defined by {@link #PARAM_CMDLINE};
404: * and the bounds of the plugin within the wiki page text by a parameter defined
405: * by {@link #PARAM_BOUNDS}, whose value is stored as a two-element int[] array,
406: * i.e., <tt>[start,end]</tt>.
407: *
408: * @param argstring The argument string to the plugin. This is
409: * typically a list of key-value pairs, using "'" to escape
410: * spaces in strings, followed by an empty line and then the
411: * plugin body. In case the parameter is null, will return an
412: * empty parameter list.
413: *
414: * @return A parsed list of parameters.
415: *
416: * @throws IOException If the parsing fails.
417: */
418: public Map parseArgs(String argstring) throws IOException {
419: HashMap arglist = new HashMap();
420:
421: //
422: // Protection against funny users.
423: //
424: if (argstring == null)
425: return arglist;
426:
427: arglist.put(PARAM_CMDLINE, argstring);
428:
429: StringReader in = new StringReader(argstring);
430: StreamTokenizer tok = new StreamTokenizer(in);
431: int type;
432:
433: String param = null;
434: String value = null;
435:
436: tok.eolIsSignificant(true);
437:
438: boolean potentialEmptyLine = false;
439: boolean quit = false;
440:
441: while (!quit) {
442: String s;
443:
444: type = tok.nextToken();
445:
446: switch (type) {
447: case StreamTokenizer.TT_EOF:
448: quit = true;
449: s = null;
450: break;
451:
452: case StreamTokenizer.TT_WORD:
453: s = tok.sval;
454: potentialEmptyLine = false;
455: break;
456:
457: case StreamTokenizer.TT_EOL:
458: quit = potentialEmptyLine;
459: potentialEmptyLine = true;
460: s = null;
461: break;
462:
463: case StreamTokenizer.TT_NUMBER:
464: s = Integer.toString(new Double(tok.nval).intValue());
465: potentialEmptyLine = false;
466: break;
467:
468: case '\'':
469: s = tok.sval;
470: break;
471:
472: default:
473: s = null;
474: }
475:
476: //
477: // Assume that alternate words on the line are
478: // parameter and value, respectively.
479: //
480: if (s != null) {
481: if (param == null) {
482: param = s;
483: } else {
484: value = s;
485:
486: arglist.put(param, value);
487:
488: // log.debug("ARG: "+param+"="+value);
489: param = null;
490: }
491: }
492: }
493:
494: //
495: // Now, we'll check the body.
496: //
497:
498: if (potentialEmptyLine) {
499: StringWriter out = new StringWriter();
500: FileUtil.copyContents(in, out);
501:
502: String bodyContent = out.toString();
503:
504: if (bodyContent != null) {
505: arglist.put(PARAM_BODY, bodyContent);
506: }
507: }
508:
509: return arglist;
510: }
511:
512: /**
513: * Parses a plugin. Plugin commands are of the form:
514: * [{INSERT myplugin WHERE param1=value1, param2=value2}]
515: * myplugin may either be a class name or a plugin alias.
516: * <P>
517: * This is the main entry point that is used.
518: *
519: * @param context The current WikiContext.
520: * @param commandline The full command line, including plugin
521: * name, parameters and body.
522: *
523: * @return HTML as returned by the plugin, or possibly an error
524: * message.
525: */
526: public String execute(WikiContext context, String commandline)
527: throws PluginException {
528: if (!m_pluginsEnabled)
529: return "";
530:
531: ResourceBundle rb = context
532: .getBundle(WikiPlugin.CORE_PLUGINS_RESOURCEBUNDLE);
533: Object[] obArgs = { commandline };
534: PatternMatcher matcher = new Perl5Matcher();
535:
536: try {
537: if (matcher.contains(commandline, m_pluginPattern)) {
538: MatchResult res = matcher.getMatch();
539:
540: String plugin = res.group(2);
541: String args = commandline.substring(res.endOffset(0),
542: commandline.length()
543: - (commandline.charAt(commandline
544: .length() - 1) == '}' ? 1 : 0));
545: Map arglist = parseArgs(args);
546:
547: return execute(context, plugin, arglist);
548: }
549: } catch (NoSuchElementException e) {
550: String msg = "Missing parameter in plugin definition: "
551: + commandline;
552: log.warn(msg, e);
553: throw new PluginException(
554: MessageFormat
555: .format(
556: rb
557: .getString("plugin.error.missingparameter"),
558: obArgs));
559: } catch (IOException e) {
560: String msg = "Zyrf. Problems with parsing arguments: "
561: + commandline;
562: log.warn(msg, e);
563: throw new PluginException(
564: MessageFormat
565: .format(
566: rb
567: .getString("plugin.error.parsingarguments"),
568: obArgs));
569: }
570:
571: // FIXME: We could either return an empty string "", or
572: // the original line. If we want unsuccessful requests
573: // to be invisible, then we should return an empty string.
574: return commandline;
575: }
576:
577: public PluginContent parsePluginLine(WikiContext context,
578: String commandline, int pos) throws PluginException {
579: PatternMatcher matcher = new Perl5Matcher();
580:
581: try {
582: if (matcher.contains(commandline, m_pluginPattern)) {
583: MatchResult res = matcher.getMatch();
584:
585: String plugin = res.group(2);
586: String args = commandline.substring(res.endOffset(0),
587: commandline.length()
588: - (commandline.charAt(commandline
589: .length() - 1) == '}' ? 1 : 0));
590: Map arglist = parseArgs(args);
591:
592: // set wikitext bounds of plugin as '_bounds' parameter, e.g., [345,396]
593: if (pos != -1) {
594: int end = pos + commandline.length() + 2;
595: int[] bounds = new int[] { pos, end };
596: arglist.put(PARAM_BOUNDS, bounds);
597: }
598:
599: PluginContent result = new PluginContent(plugin,
600: arglist);
601:
602: return result;
603: }
604: } catch (ClassCastException e) {
605: log
606: .error(
607: "Invalid type offered in parsing plugin arguments.",
608: e);
609: throw new InternalWikiException(
610: "Oops, someone offered !String!");
611: } catch (NoSuchElementException e) {
612: String msg = "Missing parameter in plugin definition: "
613: + commandline;
614: log.warn(msg, e);
615: throw new PluginException(msg);
616: } catch (IOException e) {
617: String msg = "Zyrf. Problems with parsing arguments: "
618: + commandline;
619: log.warn(msg, e);
620: throw new PluginException(msg);
621: }
622:
623: return null;
624: }
625:
626: /**
627: * Register a plugin.
628: */
629: private void registerPlugin(WikiPluginInfo pluginClass) {
630: String name;
631:
632: // Registrar the plugin with the className without the package-part
633: name = pluginClass.getName();
634: if (name != null) {
635: log.debug("Registering plugin [name]: " + name);
636: m_pluginClassMap.put(name, pluginClass);
637: }
638:
639: // Registrar the plugin with a short convenient name.
640: name = pluginClass.getAlias();
641: if (name != null) {
642: log.debug("Registering plugin [shortName]: " + name);
643: m_pluginClassMap.put(name, pluginClass);
644: }
645:
646: // Registrar the plugin with the className with the package-part
647: name = pluginClass.getClassName();
648: if (name != null) {
649: log.debug("Registering plugin [className]: " + name);
650: m_pluginClassMap.put(name, pluginClass);
651: }
652:
653: pluginClass.initializePlugin(m_engine);
654: }
655:
656: private void registerPlugins() {
657: log.info("Registering plugins");
658:
659: SAXBuilder builder = new SAXBuilder();
660:
661: try {
662: //
663: // Register all plugins which have created a resource containing its properties.
664: //
665: // Get all resources of all plugins.
666: //
667:
668: Enumeration resources = getClass().getClassLoader()
669: .getResources(PLUGIN_RESOURCE_LOCATION);
670:
671: while (resources.hasMoreElements()) {
672: URL resource = (URL) resources.nextElement();
673:
674: try {
675: log.debug("Processing XML: " + resource);
676:
677: Document doc = builder.build(resource);
678:
679: List plugins = XPath.selectNodes(doc,
680: "/modules/plugin");
681:
682: for (Iterator i = plugins.iterator(); i.hasNext();) {
683: Element pluginEl = (Element) i.next();
684:
685: String className = pluginEl
686: .getAttributeValue("class");
687:
688: WikiPluginInfo pluginInfo = WikiPluginInfo
689: .newInstance(className, pluginEl);
690:
691: if (pluginInfo != null) {
692: registerPlugin(pluginInfo);
693: }
694: }
695: } catch (java.io.IOException e) {
696: log.error("Couldn't load "
697: + PLUGIN_RESOURCE_LOCATION + " resources: "
698: + resource, e);
699: } catch (JDOMException e) {
700: log.error("Error parsing XML for plugin: "
701: + PLUGIN_RESOURCE_LOCATION);
702: }
703: }
704: } catch (java.io.IOException e) {
705: log.error("Couldn't load all " + PLUGIN_RESOURCE_LOCATION
706: + " resources", e);
707: }
708: }
709:
710: /**
711: * Contains information about a bunch of plugins.
712: *
713: * @author Kees Kuip
714: * @author Janne Jalkanen
715: *
716: * @since
717: */
718: // FIXME: This class needs a better interface to return all sorts of possible
719: // information from the plugin XML. In fact, it probably should have
720: // some sort of a superclass system.
721: public static final class WikiPluginInfo extends WikiModuleInfo {
722: private String m_className;
723: private String m_alias;
724: private Class m_clazz;
725:
726: private boolean m_initialized = false;
727:
728: /**
729: * Creates a new plugin info object which can be used to access a plugin.
730: *
731: * @param className Either a fully qualified class name, or a "short" name which is then
732: * checked against the internal list of plugin packages.
733: * @param el A JDOM Element containing the information about this class.
734: * @return A WikiPluginInfo object.
735: */
736: protected static WikiPluginInfo newInstance(String className,
737: Element el) {
738: if (className == null || className.length() == 0)
739: return null;
740: WikiPluginInfo info = new WikiPluginInfo(className);
741:
742: info.initializeFromXML(el);
743: return info;
744: }
745:
746: /**
747: * Initializes a plugin, if it has not yet been initialized.
748: *
749: * @param engine
750: */
751: protected void initializePlugin(WikiEngine engine) {
752: if (!m_initialized) {
753: // This makes sure we only try once per class, even if init fails.
754: m_initialized = true;
755:
756: try {
757: WikiPlugin p = newPluginInstance();
758: if (p instanceof InitializablePlugin) {
759: ((InitializablePlugin) p).initialize(engine);
760: }
761: } catch (Exception e) {
762: log.info("Cannot initialize plugin " + m_className,
763: e);
764: }
765: }
766: }
767:
768: protected void initializeFromXML(Element el) {
769: super .initializeFromXML(el);
770: m_alias = el.getChildText("alias");
771: }
772:
773: protected static WikiPluginInfo newInstance(Class clazz) {
774: WikiPluginInfo info = new WikiPluginInfo(clazz.getName());
775:
776: return info;
777: }
778:
779: private WikiPluginInfo(String className) {
780: super (className);
781: setClassName(className);
782: }
783:
784: private void setClassName(String fullClassName) {
785: m_name = ClassUtils.getShortClassName(fullClassName);
786: m_className = fullClassName;
787: }
788:
789: /**
790: * Returns the full class name of this object.
791: * @return The full class name of the object.
792: */
793: public String getClassName() {
794: return m_className;
795: }
796:
797: /**
798: * Returns the alias name for this object.
799: * @return An alias name for the plugin.
800: */
801: public String getAlias() {
802: return m_alias;
803: }
804:
805: /**
806: * Creates a new plugin instance.
807: *
808: * @return A new plugin.
809: * @throws ClassNotFoundException If the class declared was not found.
810: * @throws InstantiationException If the class cannot be instantiated-
811: * @throws IllegalAccessException If the class cannot be accessed.
812: */
813: public WikiPlugin newPluginInstance()
814: throws ClassNotFoundException, InstantiationException,
815: IllegalAccessException {
816: if (m_clazz == null) {
817: m_clazz = Class.forName(m_className);
818: }
819:
820: return (WikiPlugin) m_clazz.newInstance();
821: }
822:
823: /**
824: * Returns a text for IncludeResources.
825: *
826: * @param type Either "script" or "stylesheet"
827: * @return Text, or an empty string, if there is nothing to be included.
828: */
829: public String getIncludeText(String type) {
830: try {
831: if (type.equals("script")) {
832: return getScriptText();
833: } else if (type.equals("stylesheet")) {
834: return getStylesheetText();
835: }
836: } catch (Exception ex) {
837: // We want to fail gracefully here
838: return ex.getMessage();
839: }
840:
841: return null;
842: }
843:
844: private String getScriptText() throws IOException {
845: if (m_scriptText != null) {
846: return m_scriptText;
847: }
848:
849: if (m_scriptLocation == null) {
850: return "";
851: }
852:
853: try {
854: m_scriptText = getTextResource(m_scriptLocation);
855: } catch (IOException ex) {
856: // Only throw this exception once!
857: m_scriptText = "";
858: throw ex;
859: }
860:
861: return m_scriptText;
862: }
863:
864: private String getStylesheetText() throws IOException {
865: if (m_stylesheetText != null) {
866: return m_stylesheetText;
867: }
868:
869: if (m_stylesheetLocation == null) {
870: return "";
871: }
872:
873: try {
874: m_stylesheetText = getTextResource(m_stylesheetLocation);
875: } catch (IOException ex) {
876: // Only throw this exception once!
877: m_stylesheetText = "";
878: throw ex;
879: }
880:
881: return m_stylesheetText;
882: }
883:
884: /**
885: * Returns a string suitable for debugging. Don't assume that the format
886: * would stay the same.
887: */
888: public String toString() {
889: return "Plugin :[name=" + m_name + "][className="
890: + m_className + "]";
891: }
892: } // WikiPluginClass
893:
894: /**
895: * {@inheritDoc}
896: */
897: public Collection modules() {
898: TreeSet ls = new TreeSet();
899:
900: for (Iterator i = m_pluginClassMap.values().iterator(); i
901: .hasNext();) {
902: WikiModuleInfo wmi = (WikiModuleInfo) i.next();
903:
904: if (!ls.contains(wmi))
905: ls.add(wmi);
906: }
907:
908: return ls;
909: }
910:
911: // FIXME: This method needs to be reintegrated with execute() above, since they
912: // share plenty of code.
913: public void executeParse(PluginContent content, WikiContext context)
914: throws PluginException {
915: if (!m_pluginsEnabled)
916: return;
917:
918: ResourceBundle rb = context
919: .getBundle(WikiPlugin.CORE_PLUGINS_RESOURCEBUNDLE);
920: Object[] args = { content.getPluginName() };
921: Map params = content.getParameters();
922: try {
923: WikiPlugin plugin;
924:
925: WikiPluginInfo pluginInfo = (WikiPluginInfo) m_pluginClassMap
926: .get(content.getPluginName());
927:
928: if (pluginInfo == null) {
929: pluginInfo = WikiPluginInfo
930: .newInstance(findPluginClass(content
931: .getPluginName()));
932: registerPlugin(pluginInfo);
933: }
934:
935: if (!checkCompatibility(pluginInfo)) {
936: String msg = "Plugin '"
937: + pluginInfo.getName()
938: + "' not compatible with this version of JSPWiki";
939: log.info(msg);
940: return;
941: }
942:
943: plugin = pluginInfo.newPluginInstance();
944:
945: if (plugin instanceof ParserStagePlugin) {
946: ((ParserStagePlugin) plugin).executeParser(content,
947: context, params);
948: }
949: } catch (InstantiationException e) {
950: throw new PluginException(
951: MessageFormat
952: .format(
953: rb
954: .getString("plugin.error.cannotinstantiate"),
955: args), e);
956: } catch (IllegalAccessException e) {
957: throw new PluginException(MessageFormat.format(rb
958: .getString("plugin.error.notallowed"), args), e);
959: } catch (ClassNotFoundException e) {
960: throw new PluginException(MessageFormat.format(rb
961: .getString("plugin.error.couldnotfind"), args));
962: } catch (ClassCastException e) {
963: throw new PluginException(MessageFormat.format(rb
964: .getString("plugin.error.notawikiplugin"), args), e);
965: } catch (Exception e) {
966: throw new PluginException(
967: MessageFormat
968: .format(
969: rb
970: .getString("plugin.error.instantationfailed"),
971: args), e);
972: }
973: }
974: }
|