TitleAutomatically load plugin without changing searchpath in jspwiki.properties
Date17-Sep-2005 21:34:14 EEST
Versionv2.2.33
Submitterkeeskuip
Bug criticalityJSPWiki:EnhancementRequest
Browser version--
Bug statusClosedBug
PageProvider used--
Servlet Container--
Operating System--
URL--
Java version--

I would like to request an enhancement which would automatically load plugins without having to change jspwiki.properties.

I've been bitten to many times when upgrading JSPWiki and losing my production jspwiki.properties.

I would suggest the following. Let every plugin-jar include a file "ini/JSPWikiPlugin.properties". There should be one property in this file:
jspwiki.plugin.searchPath = de.java2html.plugin.jspwiki (I tested it with Java2HTMLPlugin)

Now in the PluginManager.java we need to call 'classLoader.getResources("ini/JSPWikiPlugin.properties")'. This will return an enumeration of ALL files (thus ALL plugins) in the classpath by that name. Then read that propertyfile and append it to m_searchPath.

This construction will allow us to have all code of a plugin in its own jar. The plugin would be a selfcontained unit that can be plugged in without changing any of jspwiki's own files.

It is downward-compatible and I've already coded it. Please look at the attached PluginManager.java

-- 17 September 2005, Kees Kuip

I have one more request. Often a plugin will change the stylesheet (jspwiki.css)

I would like that every plugin could include a stylesheet of its own on the page that it is used on. This could also be a property in the "ini/JSPWikiPlugin.properties" that points to the css-file to be included.

Is this something that jspwiki would like to have? I would like to program it. Let me know.

-- Kees Kuip


Hey, this is a pretty smart idea. I like it. I'm sure we can fit it in the distribution somehow :-)

As for the CSS idea... That would then be something that goes in the commonheader.jsp as a separate tag, yes? There have been requests on how to do it with script files as well, so if you can figure out something that works for both scripts and css, then yup, I would definitely be interested.

-- Janne Jalkanen


Yes, I planned to include a tag in commonheader.jsp.

How about:
<wiki:PluginsInsert type="stylesheet"/>
<wiki:PluginsInsert type="script"/>

And in the JSPWikiPlugin.properties:
jspwiki.plugin.include.stylesheet = ini/myPluginStyleSheet.css
jspwiki.plugin.include.script = ini/myPluginScript.js

I added a 'type' attribute to the PluginsInsert tag to be prepared for more types of plugin inclusions.

Does this solution cover the scripts requests? I'm already programming it now :)

-- Kees Kuip


Hmmmm, I looked through the code and I think there is a problem with including a tag in commonheader.jsp.

The problem is that I need to know beforehand which plugins are on the page. But the 'commonheader.jsp' in included before the plugin-tags are handled.

I could create a commonfooter.jsp and insert plugin-stylesheets and plugin-scritps there because then I know all plugins on the page (I could let every plugin (in PluginTag) register itself in the PageContext). A stylesheet could be included at the bottom of the page (at least it works on firefox/linux). But a different story is the scripts. How are people want to use these scripts? Do they have to be executed at the top of the page?

-- Kees Kuip


You definitely want to have the javascripts at the top of the page since some of them may need to be run at pageload. This would mean that you need to know upfront which plugins are used by a page.
On the other hand, just including ALL javascript may be the most simple approach : it will load too much (unused) javascript but modern browsers will cache them anyway so the penalty on the page-load time would be limited. So would think this to be acceptable.

W.r.t the stylesheets, I think it make sense to load them BEFORE the load of the jspwiki.css. In this way, the stylesheet of a plugin can never overrule definitions of the main jspwiki.css; which will avoid strange behaviour on a standard jspwiki site. (like * {display:none;} ) The plugin stylesheet can then only add classes to the standard jspwiki.css, not change them.

-- DF


Nah, I don't want to include all javascript. That would create a big mess.

The requirements (as I see them now) are:

  • Stylesheets of plugins should be included before jspwiki.css
  • Scripts of plugins should be included in the <head> section of the htmlpage.

I could do this:

  1. Each plugin that is executed should register themselves in the servlet-pageContext
  2. In the commonheader.jsp you include a comment "<!-- INSERT PLUGIN STYLESHEETS HERE -->" or "<!-- INSERT PLUGIN SCRIPTS HERE -->" marking the place where the code should be inserted.
  3. I create a IncludePluginsFilter.java which replaces the comment with all the stylesheets/scripts the plugins have registered.
  4. A plugin registers a stylesheet by setting the property "jspwiki.plugin.include.stylesheet" in its JSPWikiPlugin.properties.
  5. A plugin registers a scripts by setting the property "jspwiki.plugin.include.script" in its JSPWikiPlugin.properties.

Its a clean solution. It is downward compatible and it doesn't require a big change in the code that already exists. The drawback is that each page needs to be filtered which could impact performance.

How about this?


-- Kees Kuip

Hmmm, the above solution doesn't work. Somehow the commonheader.jsp is not fed through the PageFilter. Is this an error?

OK, I get it. The PageFilter only filters wiki-content and NOT jsp-pages. I need a different solution!

How about changing the jsp-page ViewTemplate.jsp ? Like this: (using JSTL)

<c: set var 'leftmenu'>
  <wiki:Include page="LeftMenu.jsp"/>
</c:set>
<c: set var 'content'>
  <wiki:Content/>
</c:set>

<wiki:Include page="commonheader.jsp"/>
...
<c:out value="${leftmenu}"/>
...
<c:out value="${content}"/>

This way I first execute the wikipages 'leftmenu' and 'content' and remember their output in a variable. After that the plugins are know. After that I display the commonheader as usual. The commonheader can now include the new wiki-tags <wiki:PluginsInsert type="stylesheet"/> and <wiki:PluginsInsert type="script"/>. After that I output the remembered 'leftmenu' and 'content'.

Any thoughts about this? (Am I allowed to use JSTL?)

Another solution would be to implement a ServletFilter and Filter out the comments (see 2.)

-- Kees Kuip


The IdeaEditorProviders touches this as well.


OK, I have programmed it and it seems to works well. All the code is in the attachment PluginIncludes.tgz(info). I used Java2HTMLPlugin.tgz(info) to test it and that version is also attached. What actions should I have to do to have a chance of getting this in the next release?

What does it do?
It will allow plugins to add stylesheets and scripts without changing any file of the jspwiki core.

Here's how I did it:
I introduced a new tag "<wiki:MarkIncludes type="script"/>". This tag will set a mark on its place and later on it will be replaced with includes of type "script". (I have now two markers in commonheader.jsp) The plugins now have support for the types "script" and "stylesheet". (IdeaEditorProviders can also make use of this construction by using a different type. Expand the method WikiContext.getIncludeText(String type) ).

I now remember every plugin on a page in the WikiContext. After interpreting the jsp-page a ServletFilter will replace the markers with the actual includes. The ServletFilter has access to the WikiContext and can therefore do the replacement.

What should a plugin-maintainer do?
It should create a file 'ini/JSPWikiPlugin.properties' and include it into its jar-file. example for Java2HtmlPlugin :

jspwiki.plugin.shortName = code
jspwiki.plugin.className = de.java2html.plugin.jspwiki.Java2HtmlPlugin
jspwiki.plugin.maintainer = ???@???.com
jspwiki.plugin.website = http://???/?????
jspwiki.plugin.scriptLocation = ini/jspwiki.js
jspwiki.plugin.stylesheetLocation = ini/jspwiki.css
  • shortName: Instead of using the className this name can also be used to start a plugin.
  • className: Previously a plugin would use a searchpath but now you should use the fully qualified className. Bot the fully qualified className and the className can be used to start a plugin
  • maintainer: Future expansion. This offers the possibility to make a page of all plugins in the system with its maintainers/websites/versions etc.
  • website: Future
  • scriptLocation: place where the scriptfile is that should be included on the place of the tag <wiki:MarkIncludes type="script"/>. This file should be included in the same jarfile!
  • stylesheetLocation: place where the stylesheetfile is that should be included on the place of the tag <wiki:MarkIncludes type="stylesheet"/>. This file should be included in the same jarfile!

Note: This change is backward compatible. You should only upgrade your plugin if you want it to add stylesheets/scripts.

The files scriptLocation/cssLocation should also be included in its jarfile. Example stylesheet:

<style type="text/css">

.java table
{
    background-color: #ffffdd;
    padding: 12px;
    border: 1px lightgrey solid;
    width: 98%;
    margin-left: 20px;
}

.java td
{
    background-color: #ffffdd;
}

</style>

Example scriptfile:

<script type="text/javascript">
  .... script here
</script>

-- Kees Kuip

Hmm... Looking at it, it seems that it only works if there is a single plugin / JAR file. I don't think this is really the idea; maybe the plugin maintainers and other things should be read using introspection from the plugin itself?

-- JanneJalkanen


OK, I thought that all plugin's are delivered in one jar-file. And I made special effort to get it from that one jar-file (only to avoid name-clashes if another plugin would point to the same resource-location). Do you have a idea how to avoid those name-clashes? Do you want to hardcode javascript/stylesheets inside the code?

As I see it the properties maintainer/locations/version etc. is information about the plugin (as in a class <-> object relation). So I don't think that those properties belong to the WikiPlugin. I see the WikiPlugin as the 'object' and the properties belong more to the 'class' side. (Sorry about the crummy explanation but english is not my native language, I hope you understand my point here)

So I think the properties are on their place in WikiPluginClass. If you really want to have those properties hardcoded than maybe the plugin-maintainers should extend the WikiPluginClass. But I kind of liked that idea that it is not hardcoded (I'm a big fan of declaritive programming). The WikePluginClass now also instantiates the WikiPlugin so in the 'JSPWikiProperties.ini' you then only need to point to that location and hardcode the rest. (But be aware of the name-clashes if you want the javascript/stylesheets in separate resources)

-- just my two cents, Kees Kuip

We could also avoid name-clashes if the Plugin was able to add some (readonly?) WikiPages to the wiki-repository. The content of those wikipages could be stylesheets/scripts. If you make them editable you also have a history and you won't loose them when reistalling plugins/jspwiki

-- Kees Kuip.

One possibility is to have a strict naming convention. You have a jspwiki_plugins.properties, which gets located as above, and that then has a list like this:

jspwiki.pluginPath = com.mycompany.foo
jspwiki.plugin.XyzzyPlugin.cssfile=flob.css
jspwiki.plugin.BobbleFilter.propertyPage=BobbleFilterProperties
jspwiki.plugin.GnobblePlugin.scriptfile=scripts/fnord.js
jspwiki.plugin.GnobblePlugin.cssfile=GnobblePlugin.css

et cetera. This would allow then just simple checks "if a page contains plugin XyzzyPlugin, we add this kind of a css file".

-- JanneJalkanen

Do I understand correctly that this is 1 property-file for ALL plugins? I now designed it that each plugin would have its own property-file. I wanted to just drop the jarfile in the container and the plugin works. With 1-property-file you have to drop the jarfile AND edit a jspwiki-systemwide propertyfile. But it can work and you should be able to reuse most of the code i wrote (If you like the markers-with-ServletFilter-solution).

Maybe I have confused you about the name-clashes but I mean this: Suppose we have the plugin BobbleFilter and GnobbleFilter. Both of them want to include a stylesheet. And both of them have included in their jarfile the resource "ini/stylesheet.css" (Both of them chose the same name!!!). This goes allright with mine solution. But it seems that you want to load them from the filesystem (Am I right here? (because you call it scriptfile/cssfile)). When you load them from the filesystem both plugins load the same file. That's what I call a nameclash.

-- KeesKuip:wq

The problem is that sometimes people like to deploy multiple plugins in a single JAR-file. Then you would have a single property file for each JAR, but for 1...N plugins, depending on how many plugins there are in the JAR file.

There are two ways to solve that name clash: either require that the style sheet is always in the form of "css/com.mycompany.jspwikiplugins.GnobbleFilter.css" or have a separate servlet that serves out these files based on something like the plugin name.

-- JanneJalkanen

OK, I understand. If we use xml-file instead of a properties-file we could support multiple plugins per xml-file (It can be done with property-files but it gets very messy). I prefer xml-property-files but haven't used them now because jspwiki seems to prefer property-files.

Are you sure you want to load from a file's? or do you want to load them from the jarfile? If you want to have the second then there is no problem because nameclashes won't occur because the one that puts multiple plugins in 1 jar file shouldn't (and doesn't) use the same resourcelocations.

-- KeesKuip

Could you look at the sources again. I made a few adjustments.

  • More than 1 plugin can be declared in a 'ini/jspwiki_plugins.properties' file.
  • The configuration is still in .properties-file (it doesn't look as messy I would thought it be).
  • The inclusion of 'types' is more general now.

Here is an example of a propertyfile:

jspwiki.plugin.1.className = de.java2html.plugin.jspwiki.Java2HtmlPlugin
jspwiki.plugin.1.tagName   = code
jspwiki.plugin.1.include.script     = ini/jspwiki.js
jspwiki.plugin.1.include.stylesheet = ini/jspwiki.css

The convention is:

  • each plugin must have a unique prefix. The prefix must only be unique in 1 propertyfile. The prefix is : 'jspwiki.plugin.<unique name>.'. The <unique name> must only be unique, I'm not using it otherwise (I choose '1' but you can choose 'a', 'zzzz', 'whatever')
  • Each plugin must have the property: jspwiki.plugin.<unique name>.className. This is must be the fully qualified classname.
  • Optional. A more userfriendly 'tagname' property that can be used instead of the className when inserting a plugin in a wikipage. You can use [{code}] instead of [{Java2HTMLPlugin}]
  • Optional. types of includes. Each include-property has the prefix: 'jspwiki.plugin.<unique name>.include.'. The text after that prefix is the type of include (here we have the types 'script and 'stylesheet', but they can be whatever you like). To really include those includes a tag <MarkIncludes type="<includeType>"/> must be inserted (I inserted one in commonheader.jsp).

How about this solution? (attached files are for JSPWiki v2.2.33)

-- Kees Kuip

This is very good, thanks!. I am including this in 2.3.26. However, I did make a small change: the config file is in XML instea of being a Java .properties -file. This makes it a LOT clearer, cleaner, and easier to work with everything.

The syntax looks something like this:

<modules>
    <plugin class="com.ecyrd.jspwiki.foo.TestPlugin">
        <author>Janne Jalkanen</author>
        <script>foo.js</script>
        <stylesheet>foo.css</stylesheet>
        <alias>code</alias>
    </plugin>
    <plugin class="com.ecyrd.jspwiki.foo.TestPlugin2">
        <author>Janne Jalkanen</author>
    </plugin>
</modules>

And yes, I am planning to extend this to every single module (filter, plugin, template, editor, etc) for JSPWiki.

-- JanneJalkanen


I like the xml also better.

Maybe it would be better to have :

  <plugin .... >
    <include type="script"> foo.js </include>
    <include type="stylesheet"> foo.css </include>
  </plugin>

Because I can see more includes coming in the future. How about an include for a manual. How about an include for the TextFormatingRules etc.

-- Kees Kuip

I see no difference between the two syntaxes: it does not matter whether you do the separation via attributes or elements. And elements in general are nicer...

-- JanneJalkanen


Hi, it seems a little bit strange to me using the ServletFilter to expand the includes from a comment previously created by the MarkIncludes Tag. Why not use polymorphism for this? Add an additional Interface for Plugins that includes a getPluginIncludes() method (keep the old one for backward compatibility). A plugin developer then could overwrite this method to add the CSS or JavaScript includes String. So for every plugin you could define wether you would like to have includes (CSS, JavaScript etc) or not. Polymorphism would do the task of including the code on top of the page for you. This would be more familiar to most developers. see Idea Editor Providers with the createIncludes() method.

--ChristophSauer


Janne, the reasons are:
  • There is one generic mechanisme for injecting includes. (So the class WikiPluginInfo doesn't change, you only have to add a tag to the jsp)
  • The dtd doesn't change if another include is invented. So the contract between plugin and jspwiki doesn't change.

Christoph, You must realize that the jsp is evaluated from top to bottom. At the top we don't know yet which plugins are on the page. So the place is marked where the include should be and afterwards (when all plugins are known) it is inserted.

-- KeesKuip


What happens if there are servlets defined by the plugins, like in ImageXPlugin? For those plugins to work one has to modify the servlet mappings in the web.xml. It would be useful if there would be a way to define this in the plugins xml file as well.

Kees, maybe we could get in contact by email to make this work with the IdeaEditorProviders. Just to discuss the details, here's my email: sauer (at domain) fh-heilbronn dot de. Thanks.

--ChristophSauer

It is verry.... complicated. For example, if you have an Apache - Tomcat connection, just simply adding the servlet might not be enough. Not to mention that I am not sure if you can add a servlet on-the-fly.

But you could add a JSP file in your archive. That should work. After all, JSPs are servlets...

-- JanneJalkanen

It seems that not all of the code is added to cvs. I miss :

Should I do something more to let this be accepted in cvs? Adding javascript/css per module just doesn't work now.

-- KeesKuip

Yes, sorry. I decided to apply the solution in partially just for now; I'm including the rest once I get it checked.

-- JanneJalkanen

Add new attachment

Only authorized users are allowed to upload new attachments.

List of attachments

Kind Attachment Name Size Version Date Modified Author Change note
tgz
Java2HTMLPlugin.tgz 0.5 kB 3 01-Oct-2005 13:07 KeesKuip
tgz
PluginIncludes.tgz 13.4 kB 4 01-Oct-2005 13:07 KeesKuip
« This page (revision-61) was last changed on 23-Feb-2007 14:53 by ChristophSauer