Objectives#

The main goal of the public api is to have a stable codebase that developers can use to extend JSPWiki. Changes in this API would imply a major release.

Starting with 2.11.0.M7, the public API is reachable at the org.apache.jspwiki:jspwiki-api:LATEST_JSPWIKI maven coordinates. Its contents are covered in the following sections.

Core API#

It's composed of the classes and interfaces in the org.apache.wiki.api.core, org.apache.wiki.api.engine, org.apache.wiki.api.exceptions and org.apache.wiki.api.spi packages, plus the org.apache.wiki.api.Release class. It models the core abstractions of JSPWiki:

  • Page, which models a wiki page, and has an associated Acl, which is composed of AclEntrys
  • Attachment, which models the attachments added to a wiki page. Conceptually, an Attachment is a kind of Page, and thus Attachment extends Page, so it also has associated Acls, author, creation date, etc.
  • Session, which models the session of a user in the wiki.
  • Context, which models the current action (view, edit, attach, etc.) happening in a given moment.
    • an action in JSPWiki is modeled with a Command, and that's why Context extends Command.
    • as the Context is modelling the current action, you can access from here the associated HTTP request, Session and Page, variables that you set on this scope, the associated user, etc.
    • The ContextEnum is a simple placeholder for all the kinds of Command identifiers understood by JSPWiki.
    • Contexts are usually created at JSP level and then passed on to the java code.
  • Engine, which is responsible of keeping the wiki running. As opposed to Context, the Engine focuses more on the "static" side of the application: configuration properties, urls (global, rss, etc.), the associated ServletContext, etc.
    • Perhaps the most important method here is getManager(Class<?> cls ) which returns the requested "manager" (or engine part, following the engine metaphor). Most of these managers are not part of the public API, so if a given extension relies on them it may stop working on a new minor release.
    • There is only one instance of an Engine for a given wiki.

The default implementation for these abstractions are:

  • Page : org.apache.wiki.WikiPage
  • Attachment : org.apache.wiki.attachment.Attachment
  • Acl : org.apache.wiki.auth.acl.AclImpl
  • AclEntry : org.apache.wiki.auth.acl.AclEntryImpl
  • Session : org.apache.wiki.WikiSession
  • Context : org.apache.wiki.WikiContext
  • Engine : org.apache.wiki.WikiEngine
warning note that these default implementations should never be instantiated/accessed directly or, generally speaking, appear in an extension code. The concrete implementation of the public API used by JSPWiki is selected through an SPI, so it is not impossible that a JSPWiki installation may be running with a different implementation.

To obtain instances from the public API you may use:

  • the JSPWiki API (public or not); for example, an Engine can be obtained by using Context#getEngine().
  • the org.apache.wiki.api.spi.Wiki class, a DSL which exposes different SPIs to obtain said instances; for example a Context can be created using one of the Wiki.context().create(..) methods, or the Engine instance can be obtained by using one of the Wiki.engine().find( .. ) methods, etc.

Providing custom core API implementations#

As noted before, the concrete implementation of the public API used by JSPWiki is selected through an SPI, so it is easy to provide custom implementations for the core API:

  • For a custom Engine, provide an implementation of o.a.w.api.spi.EngineSPI, and set the jspwiki.provider.impl.engine property on the jspwiki-[custom].properties file with the fully qualified name of the implementation.
  • For a custom Context, provide an implementation of o.a.w.api.spi.ContextSPI, and set the jspwiki.provider.impl.context property on the jspwiki-[custom].properties file with the fully qualified class name of the implementation.
  • For a custom Session, provide an implementation of o.a.w.api.spi.SessionSPI, and set the jspwiki.provider.impl.session property on the jspwiki-[custom].properties file with the fully qualified class name of the implementation.
  • For custom Page or Attachment, provide an implementation of o.a.w.api.spi.ContentsSPI, and set the jspwiki.provider.impl.contents property on the jspwiki-[custom].properties file with the fully qualified class name of the implementation.
  • For custom Acl or AclEntry, provide an implementation of o.a.w.api.spi.AclsSPI, and set the jspwiki.provider.impl.acls property on the jspwiki-[custom].properties file with the fully qualified class name of the implementation.
info note that for these SPIs to work, each of these implementations must be accompanied by its corresponding META-INF/services/$IMPLEMENTED_INTERFACE file whose content must also be the fully qualified class name of the implementation.

Registering custom managers in the WikiEngine#

It is possible to register custom "Engine parts" (managers or components or however they end up being called):

  • The default Engine implementation will look on classpath for an ini/classmappings-extra.xml file, with the same structure as ini/classmappings.xml.
  • If found, will register each requestedClass with its correspondent mappedClass.
  • These custom managers must have a no-arg constructor.
  • If there's a need to perform some initialization tasks querying the Engine, the custom manager should implement o.a.w.api.engine.Initializable and perform those tasks there.
  • Custom managers then will be accessible through Engine#getManager( CustomComponent.class ).

Please also note that

  • The classes don't get registered in any particular order, as there's an iteration of the contents of a Map.
  • As with "built-in" managers, if there are more than one requestedClass entries with the same mappedClass, the last getting registered overwrites the other(s).
  • Access to the custom manager is controlled through its access modifiers (although the Engine must be able to "see" it).

Plugins#

It's composed of the classes and interfaces in the org.apache.wiki.api.plugins package.

This part of the public API is covered in detail in the How to write a Plugin page.

Filters#

It's composed of the classes and interfaces in the org.apache.wiki.api.filter package.

This part of the public API is covered in detail in the How to write a Filter page.

Page and Attachment providers#

It's composed of the classes and interfaces in the org.apache.wiki.api.providers and org.apache.wiki.api.search package.

This part of the public API is covered in detail in the How to write a Page Provider page.

Engine lifecycle extensions#

Another SPI to allow custom components be aware of Engine's initialization and shutdown, without having to deep dive on Engine's internals. The interface to be implemented in this case is org.apache.wiki.api.engine.EngineLifecycleExtension, which is described in detail in the How to write an engine lifecycle extension page.

Event Listeners#

Yet another SPI to allow easy registering of custom wiki event listeners, without having to care on how to properly register it. The interface to be implemented in this case is org.apache.wiki.api.events.CustomWikiEventListener, which is described in detail in the How to write a custom wiki event listener page.

Logging#

Log4J2 is used to do all the logging inside JSPWiki, with all Log4J (and SLF4J) calls transparently routed to Log4J2. As such, existing 3rd party plugins, filters and providers will continue to log as expected, as Log4J calls will be routed to Log4J2, but the use of Log4J2 should be preferred onwards.

By default, JSPWiki will configure Log4J2 from the jspwiki[-custom].properties files, expecting log configuration to be there using Log4J2 properties syntax. jspwiki.use.external.logconfig=true can be set on jspwiki[-custom].properties files to bypass JSPWiki log re-configuration and rely directly on Log4J2 configuration mechanisms.

warning if you're using a JSPWiki customized .war, please ensure that neither Log4J nor any SLF4J implementation end up in your customized .war.

Testing#

Most, if not all, of the API you'll be working with will be interfaces, so the appropiate way for unit testing an extension is to mock them with a mocking library, like for example Mockito.

Said that, there's a maven artifact, not part of the public API, which holds JSPWiki end-to-end testing support classes. It should be added as a dependency following this configuration:

<dependency>
  <groupId>org.apache.jspwiki</groupId>
  <artifactId>jspwiki-main</artifactId>
  <type>test-jar</type>
  <version>LATEST_JSPWIKI</version>
  <scope>test</scope>
</dependency>

The most important class in this artifact is org.apache.wiki.TestEngine which is an Engine implementation suitable to be used while testing. It allows to mimic JSPWiki running behaviour and has a number of static build(..) methods which make really easy to have a TestEngine up and running:

package com.myorg.jspwiki.extensions;

import org.apache.wiki.TestEngine;
import org.apache.wiki.api.providers.PageProvider;
import org.apache.wiki.api.spi.Wiki;
import org.apache.wiki.pages.PageManager;
[...]

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
[...]

import static org.apache.wiki.TestEngine.with;

public class MyAwesomeTest {

    static TestEngine engine = TestEngine.build( with( "jspwiki.usePageCache", "false" ),
	    			        					 with( "jspwiki.pageProvider", "WikiPageAdapterProvider" ),
	    			        					 with( "jspwiki.attachmentProvider", "WikiAttachmentAdapterProvider" ),
		    		        					 with( "jspwiki.pageProvider.adapter.impl", "com.myorg.jspwiki.extensions.AwesomePageProvider" ),
		    			        				 with( "jspwiki.attachmentProvider.adapter.impl", "com.myorg.jspwiki.extensions.AwesomeAttachmentProvider" ) );

    @Test
    public void testPageProvider() throws Exception {
        final PageProvider pageProvider = engine.getManager( PageManager.class ).getProvider();
        Assertions.assertEquals( "com.myorg.jspwiki.extensions.AwesomePageProvider", pageProvider.getProviderInfo() );
        
        pageProvider.putPageText( Wiki.contents.page( engine, "page4" ), "some crazy text here" );
        Assertions.assertTrue( pageProvider.pageExists( "page4" ) );
    }
[...]	    							  
}
info note how TestEngine is defined with an static qualifier. This ensures the Engine is only initialized once throughout all the tests' executions.

My/An extension is not using the public API, what do I do?#

If you are the extension's author, please consider a new release upgrading your extension to use the public API.

If you are using an extension that does not use the public API, consider contacting the extension author so he/she considers a new release. In the meantime, JSPWiki includes an specific module, org.apache.jspwiki:jspwiki-210-adapters which provides backwards compatibility with extensions not using the public API.

Plugins and filters not using the public API don't need anything special, they should still keep working.

Page and attachment providers should modify its jspwiki-[custom].properties:

  • Page providers
    • jspwiki.pageProvider should be set to WikiPageAdapterProvider and then jspwiki.pageProvider.adapter.impl to the actual page provider.
  • Attachment providers
    • jspwiki.attachmentProvider should be set to WikiAttachmentAdapterProvider and then jspwiki.attachmentProvider.adapter.impl to the actual attachment provider.
warning note this module is subject to disappear at some point in the future, on a minor release, so extension authors should consider migrating to the public API.

warning provided compatibility is limited to the execution of extensions not using the public API. If, for whatever reason, an extension uses part of JSPWiki that has disappeared due to minor release versioning, then the extension will be loaded but it is bound to cause an exception when reaching that piece of code (this of course applies to extensions using the public API anyway).


Category.Documentation