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