3.3. Easily extending OLAT (How to write your own code for OLAT)

OLAT provides a extension mechanism similar to eclipse's plugin mechanism. It is of course never that advanced, but serves as the main purpose to allow developers to extend OLAT without touching the original source code

Extend OLAT using Eclipse

If you like to develop some extensions or just play with the sourcode and debug some workflows of a running OLAT you will need to run OLAT out of the Eclipse IDE. You can download the great Eclipse IDE including the necessary web tool plugins here

A step by step guide on how to set up Eclipse with OLAT can be found here.

The extension concept

To extend OLAT, you add one line in the olat_extensions.xml file pointing to your extension, and place your extension.jar file in the WEB-INF/lib directory. For starters, see the package ch.goodsolutions.demoextension or com.xyz.demoextension in the normal OLAT source code distribution.

Extension points

The following extension points are definded so far (see table below). There is an detail step by step example that includes the ActionExtension. The ch.goodsolutions.demoextension shows the usage of the SitesCreator extension element.

Table 3.1. Extension points

Extension interfaceExtension pointClasses applying the extensionDescription

org.olat.extensions.globalmapper

MapperProvider

org.olat.dispatcher.DispatcherActionorg.olat.dispatcher.DispatcherActionthe extension can obtain a Mapper and get to know the path it is associated with
org.olat.extensions.action.ActionExtensionorg.olat.home.HomeMainController...the extension can provide a link with a text and a description and define the action that happens when the link is clicked
org.olat.extensions.css.CSSIncluderorg.olat.gui.components.Windoworg.olat.gui.css.CSSGeneratorthe extension can provide a path (which it owns by using a Mapper extension where its stylesheet resides that should be included in every OLAT main window. Since all CSS definitions from all extensions share the same name space, it is useful to prefix the css classes with some short name of the extension. CSS class definitions should not start with "o_" since that prefix is used for the OLAT base system
org.olat.extensions.hibernate.HibernateConfiguratororg.olat.persistence.DBorg.olat.persistence.DBthe extension can add additional hibernate mappings here. Keep in mind that creating tables is quite difficult to do here. So this needs a separate sql script in the database dialect you are using. Any suggestions for a cool solution here are greatly welcomed!
org.olat.extensions.sitescreator.SitesCreatororg.olat.gui.control.generic.dtabs.DTabsorg.olat.FullChiefControllerif the extension wants to add one or more new sites (like "Home", "Groups", "Learning resources"), then a SitesCreator needs to be offered by the extension which in turn must return a list of SiteDefinition objects, each of which is responsible to create a SiteInstance.
org.olat.login.SupportsAfterLoginInterceptororg.olat.login.AfterLoginInterceptorControllerorg.olat.user.UserModuleModules which want to present a workflow right after login (e.g. ChangePasswordController) can add a controller by using the AfterLoginInterceptionManager.addAfterLoginControllerConfig(). The configuration (which controller to use, when to show again, should user be forced) is distributed by each module itself. (e.g. serviceconfig/org/olat/user/_spring/olatdefaultconfig.xml) No central configuration will be needed. See also the comments in AfterLoginInterceptionManager.


Multi user system events

While the extension points provide a mechanism to add your own software components to offer new functionality to users, typically having a GUI element, the multi user event (MUE) infrastructure can be used to listen to certain events within the system and execute some code when such events happen. This type of extension typically does not have a GUI for endusers, but enables you to react to new conditions.

When operating a cluster, the multi user events are distributed to the entire cluster. Therefore this concept is often used to signal about changes in module configurations.

Example: your code could register as a listener to multi user events in the identity event channel. Whenever something happens there, the event() method of your code will be called and you have a chance to do whatever you need to do. E.g you could check if the event was of type NewIdentityCreatedEvent and if so, you could add this user to a predefined set of courses that you want each user to be enrolled in.

Be aware that all OLAT events are executed synchronously. This means: when your code takes a long time to execute, you should use an executor thread to do the long-running task and release the event method as soon as possible (otherwhise the user might be waiting...).

Example code:

// In your module, listen to channel. Your module must implement the GenericEventListener interface.
coordinator.getEventBus().registerFor(this, null, PersistingManager.IDENTITY_EVENT_CHANNEL);		  

// Listen to events:
/**
 * Receive the event after the creation of new identities
 */
public void event(Event event) {
	if (event instanceof NewIdentityCreatedEvent && autoSubscribe) {
		NewIdentityCreatedEvent e = (NewIdentityCreatedEvent)event;
		Identity identity = ManagerFactory.getManager().loadIdentityByKey(e.getIdentityId());
		doWhateverYouNeedToDoWithNewUser(identity);
	}
}
		  

Table 3.2. Multi User Events

Event ChannelEventDescriptionAvailable since

PersistingManager.IDENTITY_EVENT_CHANNEL

NewIdentityCreatedEventFired whenever a new identity has been created (self registration, admin or batch creation like during an LDAP syncOLAT 6.3

OresHelper.createOLATResourceableType(InfoMessageManager.class)

MultiUserEventFired when the info message has been changed. The message is in the event and can be retrieved with event.getCommand()OLAT 6.1.1


For complete list : search for references of class MultiUserEvent in Eclipse.

How to add a new site/menuitem

In short: write your new code (ch.goodsolutions.demoextension or com.xyz.demoextension), deploy the code as a jar, add a line in the olat_extensions.xml, restart olat.

How to create a new workflow in OLAT

This section will cover the task how to implement a new workflow or change an existing workflows that starts with an user request (e.g. clicking a link in the browser gui or submitting a form with the browser). There are two ways to extend the functionallity in OLAT. First use the extension mechanism. A good example for the extension mechanism is the ch.goodsolutions.demoextension (To enable it uncomment the demoextension in the webapp/WEB-INF/olat-extension.xml and you will get a new tab in the main navigation called "demo-site"). The advantage of extensions is that they can be deployed as a single jar file containing all resources they need (properties files, translation files, images and classes). The other possibility to extend OLAT is to modify existing workflows. The main differeces between the two ways are that the resources of the existing stuff is spread in several locations and loaded automatically upon a request. Resources of existing workflows are located at:

classes  --> YOURPACKAGENAME/yourclass.java
content (velocity templates) --> YOURPACKAGENAME/_content/yourcontent.html
css files (static content) --> YOURPACKAGENAME/_static/css/yourcss.css
image files (static content) --> YOURPACKAGENAME/_static/css/img/yourimage.png
js files (static content) --> YOURPACKAGENAME/_static/js/yourjs.js
translations (properties) --> YOURPACKAGENAME/_i18n/LocalStrings_en.properties

The resources of content and translations are automatically reloaded if you switched off caching of velocity pages in the olat.properties file (velocity.cache.pages=false) and the language files (localization.cache=false).If you set up OLAT in Eclipse the IDE can also do hot code replace and replace java method bodies without restarting Tomcat. This is handy during development as the changes show up without restarting tomcat. Therefore is might be faster to develop your extension with your files in the different locations above and if you finished work move it to an extension directory where all staff is bundled and can get exported into a jar. We will first cover adding new content without the extension mechanism and later put the stuff into an extension.

Basically the following steps are necessary to create a new site with interaction in OLAT. Create a class that inherits from the BasicController class. By overriding the event methods you can listen to you events like your buttons or your created links and react according to the event. The controller class works together with a template file you specitfy as VelocityContainer you created. See the index.html file in the demo extension or any other template file under PAKCAGENAME/_content as guide to your content. The special variable "$r" gives you access to the framework methods for rendering componets ($r.render("myLink")) and variables created in your controller ($variableName).The class VelocityRenderDecorator gives you a list of methods that can be used in all velocity pages.

We will now go through all steps in detail for a simple example (The famous Hello world example!) with a link that changes to your username instead of "world" and shows an image after clicking. You will find this example in the source code of olat with all needed resources: search for ch.xyz.demoextension in the source.

  • Go to the sourcedirectory ch.xyz.demoextension and create a new class that extends from BasicController. We did this for you and named the class HelloWorldController.java. See the comments inside the class that give you helpful hints about the code. You have now a Controller class that listens to a template page. The HTML page (container) it listens to is specified as class var in the top of the class.

    myContent = createVelocityContainer("helloworld");
           

    The "helloworld.html file" you have to create in YOURPACKAGE/_content folder. And will be resolved as helloworld.html This file contains HTML and Velocity template code (For a reference of the Velocity template language see: VTL reference ). Inside the velocity template you can reference variables from your java class and also complete other components from other controllers. See for example:

    myContent.contextPut("myContentVariable", myString );
           

    The variable "myContentVariable" is accessible it the "helloworld.html" by typing "$myContentVariable" (without the capitals!)

  • Now we cover the PackageTranslator (translator) object which got automatically created for our controller. It is responsible for translating the content to the language the logged in user choosed. The translator looks for an other file that you have to create in YOURPACKAGE/_i18n. You can access it everywhere in your controller by calling getTranslator().translate("yourKey") LocalStrings_en.properties . This are java properties files where you can specify "key = value" pairs for the string you like to have translated.

  • You should now have all needed stuff: A controller class where you listen to your action. A velocity page that produces HTML output and a LocaleStrings_**.properties file for each language.

  • As there is no direct URL that you could enter in the browser to access you newly created content we have to "enable" it somewhere else. Excursus: OLAT URL's explained. Each link in OLAT is unique and gets dynamically generated for every page. The URL's contain the dynamically added component id and also an timestamp that gets incrementet with each click. So every OLAT URL is only valid once. This makes development of applications much easier as you do not have to worry about actions sended twice.

  • For testing purposes we add our content to the menu in the Home. To turn on this enable it in by uncommenting it in the "olat-extension.xml" file and restart OLAT. Make sure you also enabled the OLAT debug mode in the olat.properties file. You find the olat-extensions.xml file under /webapp/WEB-INF/olat-extensions.xml. You should now see the menu entry "Hello World!" . Clicking this link will create our controller and show our content in the content area of OLAT. But how do I add a link to this menu?

    Sure, we have to find the appropriate controller and add a link there and listen to the event the link produces and create our own controller. But first we need to find the right controller out of many. There is a handy feature in the debug mode of OLAT that helps in this case. If you started OLAT with the brasato.debug=true enabled in olat.properties you will see a green bug in the upper left corner, open the overlay and you can hover over the content to get usefull information about each component. Hover over the leftside menu top title and a layer will appear. Look for listener and you will find org.olat.test.HomeMainController. This controller listens to the links in the left side menu. This mean that this is the right place to add our "Hello World" link.

  • If you open HomeMainController you will see that it consists off the same structure as our own controller. You will find also an event method that listens for events to this controller. See inside the source and you will find the place where the link is added to the menu tree and where the event is handeld.

How to export the extension

Simply pack your olat extension project into a jar file (e.g. in Eclipse File-Export-JarFile)

To change our "Hello World" example into an extension that can be deployed as a singe jar to following steps are necessary:

  • Create file structure

    xy.test.demoextension
     controller
     _content
     _i18n
     _static
     HelloWorldExtension.java
          
  • Copy files to folders. The HelloWorldController to the controller folder and the properties files to the _i18n folder. Make sure you rename the "LocalStrings.properties" files to "LocalStrings_xy.properties" where "xy" stands for the country code like "en" for english as they are now all in the same folder. The images and optional css (Stylesheet) files belong to the _static folder.

  • Let's now create the Extension class. The extension class is responsible for creating an instance of our HelloWorldController and also to provide a visible link in the layout. Every Extenstion has therefor to implement the Extension interface. The only interface method "getExtensionFor()" is looking in a Hashmap for our extension that is is registered in the olat_extension.xml file. See below for an example. To add our extension to the ExtensionElements we have to add it first. We create a ExtensionElements object, that holds only objects of type ExtensionElement (see the table the section called “Extension points” at the beginning of the chapter for a list of the know ExtensionElement's). The HelloWorldExtension uses the ExtensionPoint ActionExtension which adds a link to the menu in the user "Home". The ActionExtension in our example is done with an inner class for brevity.

  • The only thing left is to export the extension as a jar file put it in the olat lib directory and enable your extension in the global olat_extension.xml file (see below) and restart olat and your done! If you consider sharing your extension with the OLAT community send your jar to the developer mailinglist and we will include it in OLAT or provide a download link on www.olat.org. Your help is very welcome and do not hesitate sending us your extension or ask questions on how to create one!

How to deploy the extension

Consider the file olat_extensions.xml in the WEB-INF directory. Add your bean to the olat extensions by adding a line where the comments says to add. The class attribute must point to your class which implements the Extension interface.


 <!-- generic OLAT extensions -->
 <bean id="extmanager" class="org.olat.extensions.ExtManager" singleton="true">
  <property name="extensions">
   <list>
    <!-- classes implementing the Extension interface -->
    <!-- add your extensions here! -->
    <bean id="demoextension" class="ch.goodsolutions.demoextension.DemoExtension" singleton="true" />
   </list>
  </property>
 </bean>

    

How To Create Course Building Block

You have to implement a class XXXCourseNode which extends GenericCourseNode. The XXXCourseNode class will be serialized into 'runstructure.xml'. Define non-persisting attributes as transient e.g. log . The XXXCourseNode must implement : createEditController, createNodeRunConstructionResult, calcAccessAndVisibility and isConfigValid. (see Section 2.4, “Resource Course”)

You may implement informOnDelete, cleanupOnDelete, archiveNodeData, exportNode and importNode. The default implementations does nothing.

Design decisions for building block data :

  • Do you will have data in the database ? Where do you stored the entry point to the building-block data. As an example : the project-broker store the project-broker-ID in the CoursePropertyManager.

  • Do you will have data on the file-system ? Where should the files be stored ? The files must be cleanup in method 'XXXCourseNode.cleanupOnDelete'. Do not forget to implement cleanup file-data for user-deletion! To access the course-base-container use : courseEnvironment.getCourseBaseContainer().getRelPath() .

  • You want to use the assessment-tool ? => Your course-node must implement 'AssessableCourseNode'. Be careful with addional data-structure e.g. like project-broker. The assessment-tool navigate currently only to course-nodes and not to the sub-elements.

  • Configuration : The static building-block configuration can be stored in the ModuleConfiguration (NodeEvaluation.getCourseNode().getModuleConfiguration()). This configuration will be stored in the file 'runstructure.xml' of the course and will be persisting over export/import of the building block.

    The dynamic building-block configuration can be stored in the CoursePropertyManager (course.getCourseEnvironment().getCoursePropertyManager()). This data will be removed by an export of the building block.

  • Could the new course-node implement as learning-resource ? A learning-resource is more general and could be linked-into a course.

  • Does the course-node need cluster-wide synchronization ? Does the course-node need caches ? (see Section 3.6, “OLAT Clustering and Scalability Concepts”)