OLAT

Developer Documentation

MELS - Multimedia & E-Learning Services, University of Zurich, Switzerland

Table of Contents

Preface
1. How to setup OLAT in the Eclipse - MySQL - Tomcat environment
1.1. Software and Configuration
1.2. OLAT source code
1.3. Setup your OLAT Database
1.4. Configure OLAT
1.5. Last step, Run OLAT
1.6. Optional: Use olatcore source code instead of precompiled jar-file
2. The OLAT LMS implementation concepts
2.1. Interaction design
Overview
Principle
Guidelines for interactive elements
Navigation
Action
2.2. User management
System roles and rights
Authentication providers
2.3. Resources and repository
Types and repositoryhandlers
Workflows
2.4. Resource Course
Course structure and course building blocks
Course run, User course session, Course environment
Editor - publish - run
Configuration
Visibility and accessability rules
Assessment and scoring
Groups - areas - roles - rights
Activity logging and tracking
2.5. Resource IMS QTI
Editor
Runtime
Results
Disabling of collaboration tools
2.6. Groups
System roles versus resource owners versus business groups
Generic business group and Grouptypes
Learning groups
Right groups
Buddy groups
Groupcontext
Areas
Rights
Group type based localization
Collaboration tools
IM roster synchronization
2.7. Shibboleth
Registration
Attributes
Authentication Providers
2.8. Home
Settings
Portal and portlets
Personal folder
User visiting card
2.9. Other elements
SCORM 1.2
Folders and WebDAV
Direct jumps and state activation
Context sensitive and comprehensive help
GUI preferences
3. OLAT development framework overview
3.1. Architectural overview
Introduction
System boundaries
Programming concept
Component based
Separation of logic and layout
Event dispatching
OLAT Web 2.0 Ajax mode
Features only avaiable in Ajax mode
Technical backgrond of the OLAT ajax mode
Listening to externel events in your controller
Programming for ajax mode
OLAT supported javascript libs
Ease of developing and debugging
Authentication and Authorization
Authentication: Supported providers so far are: Normal formbase username/password login, and Shibboleth 2.x login and LDAP. See Classes of type AuthenticationController.
Authorization:
GUI demo: Grafical elements in action
Proper usage of escaping in velocity pages
3.2. Application Layers
Responsibilities of the layers in short
Servlets deployed by OLAT
Windows / Chiefcontrollers
Controllers
Managers
Virtual Filesystem (VFS)
Database Abstraction Layer (Hibernate)
3.3. Easily extending OLAT (How to write your own code for OLAT)
Extend OLAT using Eclipse
The extension concept
Extension points
Multi user system events
How to add a new site/menuitem
How to create a new workflow in OLAT
How to export the extension
How to deploy the extension
How To Create Course Building Block
3.4. Elements explained
Controllers
Managers
i18n
Mappers
Properties
How to create, retrieve and delete a property
db
3.5. The util package
3.6. OLAT Clustering and Scalability Concepts
Syncer
Implementation detail
Locker
Implementation detail
Cacher
Implementation detail
EventBus
Implementation detail
Example usages of the tools
Serializing access to the database for special operations
Things to avoid
Hints and cookbooks for the four „tools“
Cacher
Syncer
EventBus
Locker
3.7. Going deeper into OLAT development
3.8. Reusable components and controller library
TextComponent and TextFactory
4. OLAT software license
5. Version Control and Branching
5.1. Feature branch
5.2. Release branch
6. Contact, mailinglists and support
6.1. Mailinglists
6.2. Support

List of Figures

2.1. How the repository looks.
2.2. The actions on the repository.
2.3. How the repository looks.
3.1. Select Debug Mode
3.2. GUI debug stack
3.3. GUI debug listeners box
3.4. Red screen of an unchecked exception.
3.5. creating a new instance of a buddygroup

List of Tables

2.1. Repository file validating classes, suffixes and handlers
3.1. Extension points
3.2. Multi User Events
3.3.
3.4.

Preface

This documentation is intended for OLAT developers. It explains the basic concepts and paradigms used in the open source learning management system OLAT.

Chapter 1.  How to setup OLAT in the Eclipse - MySQL - Tomcat environment

1.1. Software and Configuration

First of all you need the java webapp developers base pillars:

  1. Java SDK 1.6.x http://java.sun.com

  2. Eclipse 3.x http://www.eclipse.org

  3. MySQL http://www.mysql.org/downloads/mysql/5.0.html, please read the MySQL part in the techdocu for the setup OLAT Techdocu MySQL

  4. Tomcat 5.x or Tomcat 6.x http://tomcat.apache.org/ (scroll down -> Binary Distribution -> Core -> may be windows service installer )

  5. Maven 2.0.x or higher, only if you need to change or debug the olatcore project

Install those big things on your computer. Now you have to configure Eclipse 3.x to work together with the tomcat. Either you install the Sysdeo tomcat plugin or you use the eclipse Web Tools Platform WTP.

In this description we will use Eclipse 3.x together with WTP.

Follow these steps to have the Web Tools Platform (WTP) at your finger tips:

  • start eclipse

  • Help | Software Updates | Find and Install , use this to search for new features and watch out for the Web Tools Platform, select and install it.

  • typically it is recommended to restart eclipse... do so.

Further eclipse setup:

  1. check that you have a Servers entry under Window | Preferences

  2. activate Installed Runtimes and Add an Apache Tomcat runtime, configure it to fit your pathes.

  3. add a Server entry: add a new server by File | New | Other. Take the default value localhost for server and typically the preselected Apache tomcat for the server runtime.

  4. add TOMCAT_HOME classpath variable to the eclipse preferences (Window | Preferences | Java | Build Path | Classpath Variables)

So far you should have an eclipse with a tomcat at localhost configured server. But you are missing the OLAT source code. Let us then proceed to anonymously checkout the OLAT source from HEAD.

1.2. OLAT source code

The OLAT source code is splitted in two java eclipse projects: the olatcore project (cvs module: "olatcore") and the LMS (learning management system) project (cvs module: "olat3"). The olatcore project is the framework upon which the LMS is based on. Basically, one only needs the olat3 for LMS development, since per default the olatcore lib is provided as jar in the olat3/webapp/WEB-INF/lib .

For debugging purposes, there is the possibility to checkout the olatcore and link the olatcore source to the olat3 project build path (Eclipse: olat3 project Properties/Java Build Path/Source/Link Source). It is advisable that the olatcore should not be changed during the LMS developing process.

The OLAT project gives you read-only access to its sources. If you encounter bugs or have some interesting hints or questions about parts of the source - do not hesitate to write it to the developers list.

You have to open the CVS Repository Exploring Perspective, which you find via Window | Open Perspective | Other..

  1. create a new repository location: right click to create a new repository location

  2. fill in the needed fields:

    • host: cvs.olat.org

    • repository path: /usr/local/cvs

    • user: anonymous

    • password: anonymous

    • connection type: pserver

    • use default port

    • validate connectoin on finish

    • save password

  3. click on finish, this validates if you can connect... you should be connected to internet to have this working.

After successful creation of this CVS repository location you can browse your repository. Just go straight ahead to the HEAD | olat3 right click on it and check out this copies the whole project from the repository server to your eclipse workspace. This may take a while...

As already mentioned, the LMS java project already contains the olatcore jar (olat_core-1.0-SNAPSHOT.jar).

In case you also need the olatcore source:

  1. checkout the olatcore module from HEAD.

  2. Define a M2_REPO classpath variable for your maven repository.

    Define your maven repository to eclipse by running the maven command:

    • $ cd workspace_dir (go to your eclipse workspace directory)

    • $ mvn -Declipse.workspace=. eclipse:add-maven-repo

    This command creates an Eclipse classpath variable M2_REPO that points to your local maven repository.

    IF THIS DOESN'T WORK: add M2_REPO class path variable manually into eclipse (olatcore project Properties | Java Build Path | Libraries | Add Variable | Configure Variables… | New)

  3. cd olatcore (change to the olatcore directory you just downloaded)

  4. mvn clean package (download basic libs form maven-repo into local repository)

  5. mvn eclipse:clean eclipse:eclipse

If you want to work/debug in both projects (olatcore and olat3), you must change the default olat3 project settings like described in Section 1.6, “Optional: Use olatcore source code instead of precompiled jar-file ”

1.3. Setup your OLAT Database

There are two ways to create your OLAT MySQL database:

  • Use the script /olat3/bin/dbAndUserSetup.sh to automatically generate the OLAT database and user. You will be prompted to enter the mysql root password.

  • Or, create the database and user manually.

    # mysql -u root -p On the console run the following SQL-statements:
    # use mysql;
    # mysql> create database olat;						
    # mysql> create user 'olat'@'localhost' identified by 'olat';
    # mysql> grant all on olat.* to 'olat'@'localhost' identified by 'olat';
    # mysql> flush privileges;  			
    				

1.4.  Configure OLAT

  1. Create an "olatdata" folder (e.g. in your Eclipse workspace), its path will be further referred into the userdata.dir parameter of the olat.properties file.

  2. Duplicate the file olat.local.properties and rename it to olat.local.properties (properties are read by spring on startup). Customize the olat.local.properties file according to the diff below. Important are the sections "directories and server and ports". Notice: Even on windows you should use forward slashes "/" instead of windows style backward "\" slashes! Please adapt at least the following parameters:

    • base.dir=/usr/local/opt/olat/olat3

    • userdata.dir=/usr/local/opt/olat/olatdata

    • tomcat.home=/usr/local/opt/tomcat5

    • server.domainname=localhost

    • server.port=8080

    • adminemail=yourname@yourdomain.com

    • supportemail=yourname@yourdomain.com

    • smtp.host=smtp.yourdomain.com

    • db.pass=mypassword

    • brasato.debug=true

    • localization.cache=false

    • user.generateTestUsers=true

    • log.rootCategory = INFO, syslog, A1

1.5.  Last step, Run OLAT

You should have now:

  1. a java project olat3 [cvs.olat.org]

  2. a Tomcat Server @ localhost-config entry filed under Servers

  3. Optionally: a java project: olatcore

The previous step - configure OLAT - did create a server.xml file, which you will find under olat3/conf/. As a last step you copy this server.xml file to the Servers project into the Tomcat Server @ localhost-config. You get asked if you want to overwrite, just do so.

  1. copy olat3/conf/server.xml to Servers/Tomcat Server @ localhost-config

  2. in eclipse Window | Show View | Other browse to Server and choose the Servers

  3. right click on Tomcat Server @ localhost-config and choose Run

  4. the view changes away from the Servers view to the Console view and you see a lot of output from the starting OLAT. It also happens that an "Error" message pops up saying "... Server did not start after 45s".. just click ok, this is not a problem.

  5. in the console view you should find something like INFO: Server startup in .... ms

  6. Congratulations you are done!

    Point your browser to http://localhost:8080/olat.

    Login to your new OLAT installation by using administrator and olat as default username and password.

1.6. Optional: Use olatcore source code instead of precompiled jar-file

If you want to access the olatcore source code you might want to check it out. Here is an explanation of how to use the source code in your Eclipse project instead of the olatcore.jar file. This is especially useful for debugging and to get a better understanding of the olat core functionalities.

  1. Select the Repository view, expand HEAD and right click on olatcore. Choose Check Out.

  2. In order to remove the olatcore from the libraries go back to the Java view and right click on the olat3 project. Select Properties.

    Then select the olatcore jar-file from the Java Build Path Libraries list.

    Press Remove.

  3. As a next step we will link the olatcore source code into the olat3 project. In the properties window select the source tab. Then choose Link Source... Browse to olatcore/src/main/java and hit Choose. Name the Folder to something more descriptive like corejava or just olatcore.

    Repeat the same thing for the source code at olatcore/src/main/resources (Link Source... -> Browse... -> Choose -> Finish). In the Java Build Path Source list you should now have five entries:

  4. What we also have to do is to get rid of the jar-file in the WEB-INF/lib directory. Right click on olatcore-1.0-SNAPSHOT.jar and delete it.

  5. There is one more thing to do. Open the file olat.properties and set the property brasato.src to the path of your workspace followed by /olatcore/src/main/java/ (there is an example in the file). Uncomment the line brasato.src=jar. Your file should now look something like this:

    # Where to load the brasato resource. In normal cases this is set to
    # "jar". For developers that use the brasato project instead of the core
    # it should be set to the brasato project path, see example below. The
    # framework uses this path to load resources like velocity pages, i18n
    # files.
    # brasato.src must be on windows an URL: e.g. C:/eclipse/workspace/olatcore/src/main/java/
    brasato.src=/Users/me/Documents/workspace/olatcore/src/main/java/
    # brasato.src=jar	
    					

    In order for the properties to take effect, run mvn olat:properties

That's it. Now you should be able to restart the Tomcat server and run OLAT from source.

Chapter 2. The OLAT LMS implementation concepts

Summary

In this chapter you will learn which interaction design concepts have been implemented in OLAT and about the business entities used. It provides guidance to developers of the open source community to ensure a consistent user interface. Code contributions complying with these concepts are more likely to be accepted for official releases. The technical architecture and the development framework is described in chapter Chapter 3, OLAT development framework overview

2.1. Interaction design

Overview

Conceptual overview of the OLAT interaction design. Shaded entities are not in the scope of this chapter.

The most important layout elements used in OLAT

Since OLAT is a dynamic webapplication and not a static website the whole layout and navigation is generated on the fly and can't be changed by the common user. You can still customize the look and feel of your OLAT installation, this is explained in Customizing. For navigation in OLAT we separate the page in four parts: top navigation, content menu, content area and toolbox actions.

Principle

The interaction design is characterized by consistency regarding position (relative and absolute), structure, semantics, and function. I.e. interaction elements of similar types or conglomerates thereof

  • are at same absolute positions: e.g. site navigation is always on the top of the window, content menu is always on the left of the window, and tools as well as spanning actions are always in corresponding boxes on the right.

  • are at same relative positions: e.g. close command is always at the bottom of an action box.

  • have same structures: e.g. hyperlinks have same color, mouse-over behavior etc.

  • use same semantics: e.g. the search action is always called “Search” (not sometimes “Find”)

  • trigger same functions: e.g. the search action always returns a list to choose an entry.

The following guidelines do not act as laws of nature, you will certainly find exceptions in OLAT. But (intended) guideline deviation should be well-founded and should result in better usability.

Guidelines for interactive elements

  • Function: Interactive elements either aim at navigation or at action.

  • Structure: The two different functions are pointed out with a fundamentally different element design (hyperlinks and buttons). There are some exceptions described later.

  • Semantics: Interactive elements are labeled with an initial capital letter. There are no "title rules" in the English version, i.e. "Back to search" instead of "Back to Search".

Navigation

Navigation elements are visualized by hyperlinks (in site navigation, content menu, and continuous text), tabs (in tabbed panes), and icons. Their labelings never include verbs.

  1. Hyperlinks in the top navigation

    • Structure: They are characterized by the position indicator color: users can identify the active site by the coloring of the tab, inactive sites are labeled differently. Neither is underlined.

    • Function: Hyperlinks in the static and dynamic tabs always refresh the remaining screen (content menu, content area, and toolboxes). No pop-up windows and/or external sites are launched.

  2. Hyperlinks in the content menu

    • Structure: They are characterized by the two position indicators formatting and color, i.e. users can identify their position in OLAT by means of bold entries in the menu tree. Inactive locations are characterized by plain entries. Both get underlined on mouse-over.

    • Function: Hyperlinks in the menu tree always point to the content area of the own site.

  3. Hyperlinks in continuous text

    • Structure: They have no position indicator and are always plain and underlined (on mouse-over).

    • Function: Contrary to links in the menu, they can point to external locations, launch the e-mail client or navigate one step back in a workflow (see the following three CSS-classes).

  4. Tabs in tabbed panes

    • Structure: They are charcterized by the position indicator color , i.e. users can identify the active tab by means of the link and background colors. Tabs in tabbed panes never contain icons.

    • Function: Links in tabs always open the respective card.

  5. Icons

    • Structure: Linked icons are neither framed nor underlined. Mouse pointer changes on mouse over. Provide alt texts where the icon is not followed by a descriptive link text.

    • Function: Aim at visual aid for frequently used textual links, or are used to reduce text load on the GUI.

Action

Action elements are visualized either by dedicated boxes or by three types of buttons (small, main and form buttons). Labelings always include a verb.

  1. Action- and toolboxes

    • Structure: Always located on the right of the page, wrapped in a colored, entitled box. Entries are preceded by icons.

    • Function: Each box comprises similar actions. Rule of thumb: whenever possible place actions of spanning nature as hyperlinks in boxes, instead of as buttons in the content area.

    Example of an action box.

  2. Buttons

    • Structure: Very small, small, default and disabled buttons are visualized by one of the the css-classes b_xsmall, b_small and b_disabled together with b_button.

    • Function: All buttons trigger an action or workflow inside the current content area. Small buttons are used for auxiliary actions, default buttons for actions which are crucial to reach a goal. Form buttons are rendered by the class b_button and used to visualize the common html functions cancel, submit and reset.

  3. Exception: Listings

    Actions corresponding to individual entries of a listing are placed as hyperlinks in dedicated columns, i.e. no buttons are used for repetitive actions.

    As an exception, actions in listing are visualized as hyperlinks.

2.2. User management

The following outlines briefly the role/rights concept and the way users are authenticated and authorized within OLAT.

System roles and rights

In OLAT there are four basic system roles. Those roles are assigned as a basic set of rights. You can add users to those system roles in the Administration area within OLAT. Additional rights can be defined individually as well in the Administration area. The system roles are, in short:

  • guest: This is the guest system role with a limited set of rights which allows for example browsing the repository and launching content which is available to guests. All guest accounts are assigned this role.

  • user: This is the regular olat user role. All registered and authenticated users within the OLAT system are assigned this role. The role allows for the basic functionality such as personal briefcase, browsing the repository, creation of buddy groups and other.

  • author: This is the author system role. It includes all rights of the user system role but includes the author right which allows for example the creation of content in the repository.

  • administrator: This is the administrator or superuser system role. Users with this role have the whole set of rights within OLAT. In some workflows, users with this system role are treated differently from the rest of the users, giving them access to administrative functionality.

  • user-manager: This roles can manage user (create new users, edit user propertiers and delete users).

  • group-manager: This roles can administer course-comprehensive groups (groups of different courses).

  • institutional-resource-manager: This roles have author rights for all resource of certain institutional (e.g. has the same university like the author of the resource).

See also Section 2.6, “Groups” to learn more about the differences between groups and system roles.

Authentication providers

OLAT provides an abstract mechanism of authenticating users. In the olat_config.xml configuration file in section LoginModule you'll find a set of authentication providers which handle authentication and authorization. This is simply a class which extends AuthenticationController , hence you'll have full control over the workflow needed to authenticate your users. All authentication providers listed in the config file (and enabled) will be presented to the user on the login screen. The user may choose the way she wishes to authenticate herself.

At the time of writing, there are four implementations of AuthenticationController: OLATAuthenticationController implements a simple username/password authentication, ShibbolethAuthenticationController implements authentication based on Shibboleth 2.0 and the SWITCH Embedded DS (see the Section 2.7, “Shibboleth” for details on this method of authentication), DefaultShibbolethAuthenticationController just provides a link for redirecting the requests to the /shib/, and LDAPAuthenticationController which implements auth. based on an LDAP backend server.

The authentication provider system is also used for other authentication purposes where an authentication is needed.

  • RSS: The personalized RSS feed is protected using a token. This might appear in URL's therefore it should not be the same as the users password. See RSSUtil.RSS_AUTH_PROVIDER)

  • Jabber: Jabber uses also another password than the login password since it would not be save to use the other one. See ClientManager.PROVIDER_INSTANT_MESSAGING)

2.3. Resources and repository

Summary

The repository is found in OLAT under Learning resources and lists repository entries. These entries are meta information about a contained resource, typically a file. Each resource has an associated handler responsible for implementing the behaviour of the possible actions.

This is working in the same fashion as the filetypes - application association in a browser or a modern operating system. See Figure 2.1, “How the repository looks.” for an example repository listing. The type - handler mechanism is more detailed described in the section called “Types and repositoryhandlers” , and a description of the actions can be found in the section called “Workflows”

Figure 2.1. How the repository looks.

How the repository looks.


Types and repositoryhandlers

First of all the RepositoryHandlerFactory is the central point where the implementations of the RepositoryHandlers are subscribed for the types they claim. By design the type should be the string returned from the OLATResourcable.getResourceableTypeName() .

All resources in the repository refer to a file so far, but the type - handler mechanism is restricted by no means to files. The following table maps file resources and their suffixes to the corresponding handler.

The file validating class is used to check whether a certain file or archive is valid. The Handler class is responsible for a starting point of GUI workflows for the corresponding file or archive. It inform the RepositoryHandlerFactory about possible actions (see the section called “Workflows”) and which types it handles.

Table 2.1. Repository file validating classes, suffixes and handlers

File Validating ClassSuffixes/TypeHandler classComment
FileResource *WebDocumentHandler

A generic file resource for files not categorized by the above.

AnimationFileResource SWFWebDocumentHandler 
OLAT Course CourseModule.ORES_TYPE_COURSE CourseHandler

The OLAT course is a ZIP file containing a runstructure.xml, editortree.xml and linked other resources like tests, surveys.

DocFileResource DOCWebDocumentHandler 
GlossaryResource ZIP GlossaryHandler Archive must contain either a glossary.xml or a glossary.textmarker.xml
ImageFileResource JPG, JPEG, GIF, TIFF, IMG, BMP, PBM, ICO, PICT, PNGWebDocumentHandler 
ImsCPFileResource ZIPImsCPHandler

the extracted archive must contain the imsmanifest.xml which must validate.

MovieFileResource

MPG, MPEG, QT, RM, RAM, AVI

WebDocumentHandler 
PdfFileResource PDFWebDocumentHandler 
PowerpointFileResource

PPT, PPS

WebDocumentHandler 
ScormCPFileResource ZIPSCORMCPHandler

the extracted archive must contain the imsmanifest.xml which must validate.

SharedFolderFileResource FileResource.SHAREDFOLDER SharedFolderHandlerComment
SoundFileResource

MP3, WAV, RA, MIDI

WebDocumentHandler 
SurveyFileResource ZIPQTISurveyHandler

the extracted archive must contain the qti.xml which must validate.

TestFileResource ZIPQTITestHandler

the extracted archive must contain the qti.xml which must validate.

WikiResource ZIPWikiHandlerZIP File is scanned for a index file.
XlsFileResource XLSWebDocumentHandler 


Workflows

Managing the repository involves adding, creating new resources, removing existing ones and update the meta information of a repository entry. Adding involves either uploading a file resource or selecting one from the various folder places. Creating is typically only possible if OLAT offers the appropriate editor.

Figure 2.2. The actions on the repository.

The actions on the repository.


The concrete implementation of the RepositoryHandler, i.e. WebDocumentHandler, must provide a controller for adding the given resource. This controller is returned when the getAddController(..) method is called.

Before a deletion is issued the handler of the respective resource is asked if the resource is readyToDelete(..). The deletion is initiated by cleanUpOnDelete(..) which is expected to do any work necessary, before removing the resource definitively.

Similiar to the adding of a resource, the handler also may provide the facility to create or edit the resources meta information. This is the so called details view and a component for this can be provided in the method getDetailsComponent(..).

Figure 2.3. How the repository looks.

How the repository looks.


2.4. Resource Course

Summary

The course resides in the repository as a learning resource of type course. It is a special kind of learning resource as it has the ability to aggregate other learning resources, except courses.

It is a structural container for the course building blocks. The course is structured as a tree. And this structure is defining a learning path by means of visibility and accessability rules. The structure also offers an assessment and score facility to evaluate the course viewers progress.

It is an administrative container for the course. As it may have associated groups, areas, rights to control and organize course access.

It is important to understand the difference between the run and editor characteristic of a course. And also the ideas of the publish mechanism, which defines the transition from the editor view to the run view.

There is a course configuration with course wide settings.

Course structure and course building blocks

The course is hierarchly structured - a tree - whereas each course node represents a building block. Such a building block is an encapsulated functionality for the course. These building blocks can be divided into the categories Content and structure , Assessment and Scoring , Collaboration . Each building blocks can control access and visibility with help of the visibility and accessability rules . Following building blocks are implemented so far:

  1. Content and structure

    • Structure, STCourseNode :

      Structure element, noteworthy is the ability to calculate a score or passed value from other assessable course nodes.

    • External Page, TUCourseNode :

      An URL pointing to an external resource is integrated into the LMS without a HTML frames construct. Hence special requirements to the external resource apply.

    • CP learning content , CPCourseNode :

      Reference another learning resource of type IMS content packaging from the repository.

    • Single Page, SPCourseNode :

      Simple HTML page to display, either create one with the internal HTML editor or reference one from a storage folder.

    • SCORM learning content, ScormCourseNode :

      Reference another learning resource of type SCORM package from the repository.

    • Self-Test, IQSELFCourseNode:

      Reference another learning resource of type self test based on IMS QTI from the repository. It is listed here instead of assessment because a self test does generate only anonymized results.

    • File-Dialog, DialogCourseNode:

      Collection of files users can upload and discuss. Uses a forum compoment for each file to be discussed.

    • Linked List , LLCourseNode : This is contribution of the BPS GmbH. It allows to manage web links.

  2. Collaboration

    • Folder, BCCourseNode

      File exchange between course participiants.

    • Contact Form, COCourseNode

      Send an e-mail to a configured recipient group from the logged in user.

    • Enrolment , ENCourseNode

      As a precondition for the enrolment groups or areas must have been configured for the course. If this is the case an automatic enrolment to the course can be realized here.

    • Date Enrolment , DENCourseNode : This is contribution of the BPS GmbH. It allows to manage dates and enrolments to this dates.

    • Forum , FOCourseNode

      Discussion forum.

  3. Assessment and Scoring

    • Assessment , MSCourseNode

      Configuring an assessment building block influences the assessment tool which is at disposal to the course tutors. Check the description of the assessment and score facility.

    • Test, IQTestCourseNode:

      Reference another learning resource of type test, based on IMS QTI, from the repository. The results from the test can be accessed in the visibility and accessability rules.

    • Questionnaire , IQSURVCourseNode

    • Task , TACourseNode : A complex a building block to distribute, collect, assess given tasks.

    • Topic assignment , ProjectBrokerCourseNode : A complex a building block to manage a list projects (e.g. master-theses).

Course run, User course session, Course environment

The CourseFactory ensures that a course is loaded only once into the RAM. Thus all cours participants, tutors, authors are sharing the same course object. It is clear that each course user must have its individual course run session where user specific data is processed. To accomplish this the course has a UserCourseEnvironment which holds the user specific course run data. Besides this the CourseEnvironment aggregates runtime course information which again is shared between all users.

Editor - publish - run

The course is made persistent with the XStreamHelper using XML object serialization. Because of the sophisticated publish mechanism there is a persistent valid course editor xml tree structure and a valid course run xml tree structure co-existing. The course run is the users view to the course, and the course author sees the course in the editor view.

The author can select specific or all changes in the course editor to be published into the run structure. This process is defined in the PublishProcess and controlled from the PublishController roughly speaking it consists of:

  • removing nodes marked for deletion from the editor and run treemodels

  • copy new added nodes from the editor treemodel to the run treemodel

  • apply changes in existing nodes from the editor to run treemodel.

Configuration

Each course has associated a course configuration which is persisted as XML file in the same place as the run structure and editor strucuture files.

This settings contain so far configurations for:

  • Course chat enabled/disabled:

    A change in this setting takes effect only in new started course sessions.

  • Individual course CSS layout option:

    A change in this setting takes effect only in new started course sessions.

  • Specifying a resource folder which is shareable between courses.

  • Enabling/disabling evidence of achievement:

    Changes in this option trigger the deletion of existing achievements, or a recalculation respectively. It is clear that the recalculation may take a while.

If a new configuration must be added one has to follow the guidelines described in CourseConfig . Special care must be taken to increase the CourseConfig 's version number, once a change was active in a productive environment.

Visibility and accessability rules

This rules are specified like arithmetic expressions, whereas the valid functions and units are registered in the ConditionInterpreter. Each building block has associated a visibility and accessability condition which is recursively evaluated during the users course run. Once a building blocks visibility or accessability is false the child nodes are not evaluated.

Assessment and scoring

Some of the course building blocks are of type AssessableCourseNode. The assessable course nodes use the AssessmentManager from the course environment to set and get assessment information.

TODO:More info on this will be added soon.

Groups - areas - roles - rights

See Section 2.6, “Groups”

Activity logging and tracking

See Logging and Statistics

2.5. Resource IMS QTI

Resources of type IMS QTI accept IMS QTI v1.2.1 compliant tests and surveys. The package (with the QTI XML source and media files) comes as a simple ZIP file. The QTI XML has to be in the root of the ZIP an named as qti.xml. This type of resource can either be launched directly from the repository as a self-test where no results get persisted or included in a course via test and survey building blocks.

Editor

OLAT includes an editor for QTI files. The editor offers single and multiple choice, fill in blank and free text as type of questions (the latter only in surveys). Since QTI does not define different types of questions, the editor relies on a nameing scheme to identifie the type of question. This is done via an encoded item id in the form QTIEDITOR:SCQ:123 where QTIEDITOR denotes that this item was created with the OLAT editor, SCQ denotes the type of question (other types are MCQ, FIB and KPRIM) and finally the number is a file unique ID. See QTIEditorHelper for details.

It is important to note, that the editor does not allow to work with arbitrary QTI files created by other editors or manually. It can only edit questions which originate from the editor itself. If you do any manual changes to the generated qti.xml file, your changes may get lost when loaded again in the editor or the item may not be editable anymore. The qti.xml is parsed and converted into an internal structure within the editor. Upon saving the file, the qti.xml is generated from scratch based on the internal structure.

Runtime

The QTI runtime implemented within OLAT can handle almost all features of QTI 1.2.1, much more than the editor can actually handle. So you may not be able to edit files generated manually or by external editors but your files are most probably fine for the runtime.

The runtime allows tests to be resumed in case the user's browser crashes. For this, a dump of the whole runtime information is written to the filesystem after every response. These files are stored in PATH_TO_OLATDATA/qtiser/USERNAME/QTI_RUN_ID/qti.ser . If the user starts the test again, the workflow will resume based on the information stored in this file and the user should start again from where she left last time.

Results

The results of a test run are stored in XML files based on the IMS QTI Results Reporting standard. The source of the questions is added to the results reporting for further reference. The files are stored in PATH_TO_OLATDATA/resreporting/USERNAME/QTI_RUN_ID.xml . The results reporting XML is transformed with a stylesheet to present the results in HTML to the user. You'll find the stylesheets in the package org.olat.ims.resource.xsl. The stylesheets come in different languages where the language specific strings are defined in the prologue. This is the last bit of XSLT within OLAT.

Disabling of collaboration tools

In a real world test situation, people may not want students to be able to exchange information with each other during the actual test. For this reason, if in a test (this does not aply for self-tests of surveys), all collaboration tools will be disabled. These include the instant messaging system and the buddy groups for the specific user. All collaboration tools will be re-enabled, when the user successfully finishes her test.

2.6. Groups

OLAT has a quite advanced group management system that can be used in courses to manage learning or right groups. In a special mode the groups can even be used withing several courses. The same course concept is also used completely independent of courses in so-called project goups. In this chapter you will learn how this all works

System roles versus resource owners versus business groups

First it is important to understand which groups we are talking about. There are several places in OLAT where you can add users to a group:

  • System roles: The system administrator adds users to the system roles groups like 'authors' or 'administrators'. The system uses some SecurityGroup objects to store this information. The group membership is not directly visible to the members of this groups, they can't contact each other or something, they don't see who else is in the group. The idea is to manage rights (you can also say roles) on a system wide level.

  • Resource owners: Whenever a resource is created someone must be responsible for this resource. Usually it is handy when this right can be shared by several people. The resource is then owned by a group of people. The most obvious place where this happens is in the learning resources repository. Creating a new learning resource, e.g. a course, creates also a SecurityGroup for this resource and adds automatically the creator of the resource to this group. Everybody in this group can now manage the resource, including the management of the owner group of this resource. Adding another user to the group makes the other user instantly to an equal owner of the resource. For example could the other user then remove the creator of the resource from the resource owners group and he would have no more access to the resource although he was the original creator. This is very much intended this way - try trusting your people :-). Compared to the system roles group the group is visible to the members, however the group offers no groupware features since the only purpose is to manage access rights. Other examples for such resource owner groups are the catalog entry owners or the groupmanagement owners (comprehensive group management).

  • Business groups: Now whenever the group is the center of your actions, when the group itself is the main business case then we use so-called BusinessGroup . Business groups server the purpose doing something together as a group, maybe using groupware tools, maybe supervised by a coach. This groups have a name and a description and can be started in a runtime environment to work in the group. This is very different from the system roles or the resource owners group - those have no name and can't be started in a runtime environment since the purpose of those groups is only to manage access rights and not to communicate or collaborate with the members of the group.

In this chapter we talk about the generic business groups concept and where it is used within OLAT.

Generic business group and Grouptypes

The BusinessGroup class is a concept for a group that serves the purpose of collaborating, working or doing something together. The BusinessGroup is abstract in a way that it generically implements a set of functions that can be usefull when working in a group. However, in reality we deal with concrete groups that serve a special purpose and will use only a subset of all functions available in the generic BusinessGroup. To distinguish these concrete groups we give the BusinessGroup a field called groupType .

When thinking object oriented you could create an abstract superclass and use inheritance to create the concrete implementations. However, when persisting such a construct using a object-relational mapping layer like hibernate it can easily get complicated. Therefore we decided to make it differently and implemented one generic BusinessGroup together with one generic BusinessGroupEditController and one generic BusinessGroupMainRunController . The controllers offer the complete set of functions implemented by the generic BusinessGroup. When creating these controllers using the BGControllerFactory a configuration object BGConfigFlags is used to tell the controllers which functionality is enabled for the concrete group that is displayed or edited. This way the controllers must not know anything about group types, they only know which feature to display and which one to hide. Adding new features to groups or changeing features is only a matter of implementing the generic functionality in the businessgroup and the controllers and then enabling it in the configuration for all group types you want.

A BusinessGroup uses two SecurityGroup labled owners and participants. A SecurityGroup is a generic implementation of a group without any context or business meaning by itself. The meaning of those two underlying groups in the BusinessGroup class is that members of the owners security group have the right to modify / manage the business group wheras members of the participants security group do not have such a right.

Learning groups

Learning groups are used in the course groupmanagement for

  • Administrative purpose: Organizing students using the enrolment course building block or by adding them manually to the groups. Adding coaches to a group gives the coaches access to the administrative tools like the assessment tool for those users he is coaching. Note that without having your users organized in at least one learning group in the course you will not be able to see any course results for your participants since you just have no clue who is in your course anyway. Moste courses should have at least one learning group.

  • Didactical purpose: Students should collaborate using the collaboration tools or the the author want to create a certain learning flow (also called learning experience) using accessability and visibility conditions on the course building blocks using the group membership constraints.

The distinction between those two is usually not so sharp and very often mixed. For both reasons you would create a learning group in a course.

Learning groups use the owner SecurityGroup for the coaches and the participants SecurityGroup for the students

Learning groups do not use the group rights feature but they use the area feature. More on this later.

Learning groups uses also the IM roster synchronization feature. Can be configured and the default does the synchronization

Right groups

Right groups are used in the course to give some users certain administrative rights within the course. Usually you also want this users to be able to communicate together using a forum or to share documents.

Example 1: allow the professor to see and modify all assessment data but don't allow him to mess around in the course editor. In this case you would be in the owner group of the learning resource which makes you to the course administrator. The professor would not be in this group but you would create a new right group within the course and add the professor to this group. For this right group you would enable the assessment tool.

Example 2: you want all your coaches be able to manage all groups in the course. By default, coaches do not have full access to the groupmanagement and the assessment tool. The can launch the tools, but they have only access to the groups they are coaching. In some cases you want to give them access to all students, even to the one they don't coach. In this cenario you would create a right group and add all coaches as members to this group. Then you would enable the groupmanagement and the assessment tool for these group.

Right groups do not use the owner SecurityGroup at all. All users are members of participants SecurityGroup

Right groups use the group rights feature but they don't use the area feature.

Right groups do not use the IM roster synchronization feature.

Buddy groups

Buddy groups (in the GUI they are called project groups, in the code they are named buddy group) are groups used for self organized learning or any kind of collaboration. They are created in the groups site by any user and managed by the participants of the group. There is no way to control such a group, therefore it shold not be used in the course context. The idea of buddy groups is that every user in OLAT can create his own peer network and collaborate or share for whatever purpose. This can be a semester work by three people, a discussion room for a clique of climber enthusiasts or a place where someone that is abroad for some month can post all the images to keep in touch with the friends stayed at home.

Buddy groups use the owner SecurityGroup for users that should be able to invite other users. Regular users are members of the participants SecurityGroup

Buddy groups do not use the group rights feature nor do they the area feature.

Buddy groups use the IM roster synchronization feature. Can be configured and the default does the synchronization

Groupcontext

Correlation between group context, groups, areas and courses

Every BusinessGroup has a reference to a BGContext and the BGContext has references to OLATResource. The idea of the business group context is to define the context in which the group is used.

Groups of type BuddyGroup are an exception since they have no dependecies to any OLATResource, thus they have not context defined (NULL)

The BGContext serves as a container for all groups that belong together. It has a type which means that a BGContext can only have groups of one type in it. BGArea, as we see later, do also belong to a BGContext.

Group contexts are used in course to manage groups. Every course has at least one group context for the type LearningGroup and one for the type RightGroup. When creating a new course a new learning and right group context is generated and labeled as a default context. A default context means that this context has been generated automatically for the purpose of the groupmanagement within this course. It is only good for this course.

In most situations this is fine and everything works as expected by using this default group contexts. But in some cases it makes sense to use the same groups in several courses. This is also called course comprehensive groupmanagement.

To achieve this it is possible to create BGContext that are not labled as default context. These group contexts are created manually in the groupmanagement site that is only available to users with the system role administrator or groupmanagement. Those BGContext do use a SecurityGroup to manage the owners of this context, to define who is allowed to manage this group context. To those contexts the owners can add one or more OLATResource (meaning courses) where this groups should be used.

Note that you can have more that one group context added to a course. When launching the groupmanagement in the course you then have to select which group context you want to manipulate. Everyone who has access to the groupmanagement within a course can modify the groups as if it where a default group context containing course internal groups. They do not need to be in the contexts owner security group.

Areas

A BGContext can have so-called BGArea relations. An area has a name and description and is used to associate many groups to a certain area of a course. In the GUI we call it learning area.

You can see areas as a group of groups, however a group can be in more than one areas, thus it is not really a group of groups. You could also see an area as a group attribute, a tag that you stick on a group.

From a paedagogical perspective it makes sense to ask “are you in group red, blue or green”, then do something. However, this gets very complicated with may groups. It would be much easier to ask “are you in a color area” and then add all your groups to this color area. This is what the BGArea is good for: address and organize groups that fullfill the same purpose.

Example: create an are enrollment and add your groups Class Monday, Class Friday to this area. In the enroment course building block use the area instead of the group names to define which groups can be used for enrolment. OLAT automatically offers the user all groups of the defined learning area to enroll into.

You can also use the areas to define learning activities and associate groups to this activities. In the course building blocks you then restrict access and visibility of certain blocks to groups that participate in the named activity.

Areas are only used by business groups of type LearningGroup

Rights

Group rights can be associated to a BusinessGroup. All possible rights are defined in CourseRights, a right is nothing more than a string that has a business meaning. Using the BGRightManager it is possible to ask OLAT wheather a user has a certain right in a certain group contex.

Currently group rights are used in course right groups to grant full access to the course editor, the assessment tool, the groupmanagement or the archive tool.

Rights are only used by business groups of type RightGroup

Group type based localization

Since the whole BusinessGroup codebase is completely contextless implemented and uses the same code for buddy, learn and right groups a special issue must be covered and this is localization. On screen the generic code must have group type dependent locales. In some locale strings only the group type differs, in others it makes sense to have different sentences like when sending mails after beeing added by someone in a group

To solve the problem of one generic code and multiple translations depending on the type we use the translator fallback mechanism. Each Translator can have its fallback translator in case the key is not found. This way it is possible to build a chain of translators. The GUI controllers of the group package use the BGTranslatorFactory to generate a package translator that use a group type translator in the first position of the translator chain.

In the package org/olat/group/ui/run a certain key is used to display someting in a group, e.g. a title. As soon as you define the same key in the package org/olat/group/ui/buddygroup or org/olat/group/ui/learninggroup the key from the group type specific package has precedence and is used for translation.

Collaboration tools

Each BusinessGroup has CollaborationTools that are used in the groups run time environment to communicate and share data. The collaboration tools are generated using the CollaborationToolsFactory and are not group type specific.

The group owners or administrators can enable and disable the collaboration tools in the edit mode of the group on a group level.

Currently we have the following tools available:

  • Forum for group discussions. The forum supports notification subscriptions about modifications using email and RSS.

  • Folder for file sharing. The folders are mounted as group folder in the personal WebDAV folder that can be mounted like a local folder on your desktop and allows manipulating the files using drag and drop. The folder supports notification subscriptions about modifications using email and RSS.

  • Chat for synchronous discussions. This is only available when the Instant messaging module is enabled.

  • Contact form to contact all group members

  • Info message to display a welcome or info message that is shown when starting the group in the runtime environment.

  • Members list shows the list of members.

  • Wiki a Wiki used only in the group context

IM roster synchronization

The BusinessGroup allows synchronization of group members with your Instant Messaging roster list. This features is only available when the Instant messaging module is enabled.

Synchronized groups appear in the top navigation night next to the IM status. In brackets two figures are show: number of online buddies and number of total buddies. Buddies in this terminology are users that are in a buddygroup that you participate.

IM roster synchronization is only enabled for business groups of type BuddyGroup and can not be configured.

2.7. Shibboleth

OLAT comes with two Shibboleth 2.0 compliant authentication providers. This means, OLAT integrates with your existing Shibboleth infrastructure for authentication and you may use the attributes released by your Shibboleth Identity Provider to restrict access to courses.

OLAT needs a Shibboleth Service Provider (SP) to protect the resource , and to provide the attributes for the authenticated identities. For an introduction about how install and configure a Shibboleth Service Provider 2.0 with Apache, mod-shib, shibd (Shibboleth daemon), mod_jk see OLAT Installation & Administration Documentation.

OLAT uses the SWITCH provided Embedded Discovery Service, which gets configured via the shibbolethlogin.html.

OLAT retrieves the shibboleth attributes directly from the request in org.olat.shibboleth.ShibbolethDispatcher. Just set the loglevel to DEBUG for the org.olat.shibboleth.ShibbolethDispatcher to get the Shib attribute Map for an authenticated user in olat.log.

OLAT requests a minimum set of attributes from the Identity Provider. The required attributes are: Shib-SwissEP-UniqueID, Shib-InetOrgPerson-mail, Shib-Person-surname, Shib-InetOrgPerson-givenName, and Shib-SwissEP-HomeOrganization.

Registration

When a user first successfully authenticates herself via Shibboleth, she must register with OLAT. The user is asked for a username which identifies the user within OLAT. A user profile is generated and the email address provided by the Identity Provider's Attribute Authority is automatically added to the profile. This implies that an Identity Provider's Attribute Authority must at least provide an email address attribute. Furthermore, a unique identifier is needed which is configurable per site in the olat_config.xml. After accepting the disclaimer, the user is forwarded to the home screen and registration is completed.

Attributes

Shibboleth SP provides attributes of a user to OLAT. These attributes are propagated within OLAT upon each successful authentication. The attributes can be used within course building blocks to define access and visibility rules. Note that these attributes are not persisted, except for the unique identifier (used to associate an authenticated user to its OLAT user profile) and the email address.

For easier handling of attributes, you may define a set of attribute translations in olat_config.xml. Attributes will be available by their translated name (outName) within OLAT. For example, a standard attribute defined by Shibboleth is Shib-InetOrgPerson-givenName which is both hard to remember and enter into form fields. With the attribute translation map, you can translate this attribute's name to givenName and reference it in your accessibility and visibility rules with its translated name.

Authentication Providers

DefaultShibbolethAuthenticationController just provides a link for redirecting the requests to the /shib/. The user is farther redirected to the central WAYF, and upon selection of the IdP, to the IdP authentication page. Upon successful authentication the user is redirected again to OLAT.

Login sequence with DefaultShibbolethAuthenticationController: OLAT Login page: Shibboleth Login - DefaultShibbolethAuthenticationController > Redirect to /shib/ > mod_shib > Redirect to WAYF, choose IdP > Redirect to IDP > IdP Authentication > redirect to the Service Provider that protects this Resource (see SP configuration in shibboleth2.xml) > OLAT (ShibbolethDispatcher - retrieves the attributes)

SWITCH Central WAYF

Authenticate to your IdP

ShibbolethAuthenticationController uses the SWITCH Embedded DS. It basically works like the one above, except that the user already gets directly the WAYF on the login page.

Login sequence with ShibbolethAuthenticationController: OLAT Login page: Shibboleth Login Embedded WAYF, Choose your IdP (ShibbolethAuthenticationController) > Redirect to IDP > IdP Authentication > redirect to the Service Provider that protects this Resource > OLAT (ShibbolethDispatcher - retrieves the attributes)

SWITCH Embedded WAYF

Authenticate to your IdP

2.8. Home

The home site is the most personlized place in OLAT. This site is owned by the user and gives the user access to his personal things. Most things in the home are personal lists of something: list of bookmarks, notifications, notes and a list of efficiency statements from courses the users participates.

Settings

In the settings area of the home site the user can personalize OLAT. This means language, font size (three different CSS for small, medium, large), instant messaging preferences and personal attributes like birth date. A language change will only be activated at the next login. See also

If you need more user properties see olat_userconfig.xml for custom configuration of user properties.

In WEB-INF/olat_config.xml in the section org.olat.user.UserModule offers some more configuration options for user settings.

Portal and portlets

The home site has a portal entry page. The portal can be configured. This means the portal elements, so-called portlets, can be moved around in the portal and portlets can be activated / deactivated. The state is persisted for each user in the GUI preferences. See the section called “GUI preferences” for more info about this.

The portal, the portlets and the default portal configuration is defined in WEB-INF/olat_portals.xml . In the PortletFactory part all known portlets are defined giving them an identifyer called name. In the second section, the PortalFactory, two portals are defined: the homeportal and the guestportal

Every portal has a map called portletsConfigurations. In this map all portlets allowed in this portal are listed. The key identifies them within the portal. The values are portlet configuration values that are handed over to the portlet when launched. One special value is called portletName which referres to the name of the portlet defined in the PortletFactory.

Every portal has a list called portalColumns. This is the default list of activated portlets used whenever the user has no personalization made. The list contains lists (the portal columns) which contain references to the portlets configurations above.

Personal folder

Every user has a personal space to store files. In the users personal folder two virtual subfolders are present, the private and the public folder.

The private folder is only visible and accessable to the owning user. The public folder can be accessed by all OLAT users using the search workflow also located in the home site. Other users have only read only access to other users public folders. To share data in both directions one can use the project groups which does also feature a folder.

The personal folder can be mounted using WebDAV, see next chapter on this.

User visiting card

Every user has his own visiting card to expose more or less of his personal data to all registred OLAT users. He can upload an image and enter a text in Wiki syntax to appear on his card. In addition his public folder is visible to other users und he can be contacted by email via the contact form. If the user posts a message to the forum, the uploaded image will be integrated into the message header. Via the usersearch form a user, respectively his visiting card can be found.

The visiting card should be embedded as link wherever more information on a person should be displayed like for example in a forum post. To generate such a link together with the users portrait foto use the DisplayPortraitController.

2.9. Other elements

SCORM 1.2

OLAT has a fully compatible SCORM 1.2 runtime environement. Due to some SCORM concepts it differs a lot from the IMS content packaging viewser also integrated in OLAT. Each content object (Sharable Content Object [SCO]) communicates with OLAT over a separate channel with backend while the user is browsing the content. The API each SCO is looking for consits out of 5 javascript functions that need to be present in the SCO content.To communicate with the backend we relay on the not yet so commonly used XMLHttpRequest javascript object. It allows us to do asynchronous SCORM API call without the need for an Java Applet or other plugin technolgies.

The SCORM backend consits of three parts. First part is a standard OLAT controller which listens to the SCORM API calls and passes them further to the SCORM backend. The controller delivers also the content which is displayed in an IFrame. The API calls are passed to the API Adapter which is used in other projects as well (Ilias and ATutor).

Folders and WebDAV

All folders accessible through the GUI within OLAT are managed by the FolderRunController. The actual file operations are further wrapped by a Virtual Filesystem (VFS). See the section on the section called “Virtual Filesystem (VFS)” VFS for further details. Folders can have security restrictions such as read/write restrictions and quotas. All this is implemented through the functionality provided by the VFS abstraction layer.

All of a user's folder are exported via WebDAV through a single unique mountpoint at webdav://your.server.com/olat/webdav/. The user authenticates via an OLAT authentication provider (see section on authentication) and is presented its personal briefcase, and any coursefolders/groupfolders/sharedfolders she might have access to.

Direct jumps and state activation

After log in the home site is activated. However, it is also possible to directly jump into a course, forum in a group etc. When creating new sites or dynamic tabs this is handled via ControllerFactory.createLaunchController(). When the site or dynamic tab already exists the activation is done through the Activateable interface that must be implemented by such sites.

Context sensitive and comprehensive help

OLAT should be easy to use without the need to read help. However, the functionality of OLAT is so big and some of it is quite complex that it is not possible to make everything self explanatory. The context sensitive help is used wherever some help might be needed. The context sensitive help is appears with a small symbol. By clicking the symbol a pop up window opens containing the help.

The context sensitive help is placed in the html files using the velocity helper rendering method velocityRenderDecorator.help(). The context sensitive help is written using the translation tool explained in Customizing.

The comprehensive help in OLAT is accessed via the top navigation tools. The help opens in a new window and is organized as a regular OLAT course. This is very handy since the help has also a forum. Make sure you register to this forum notifications!

The OLAT help course is imported when starting OLAT. Modify the lines in the module CourseModule in the WEB-INF/olat_config.xml file if you want to have your own help course used instead of the default one.

GUI preferences

Some GUI elements in OLAT offer configuration / personalization options. These options usually have a very local scope and do not influence the whole system. In contrary the users settings that can be changed in the home site (see the section called “Settings”) have a global scope and affect OLAT as a whole for this user.

Every user has one object GuiPreferences that can be retrieved from the users user session ureq.getUserSession().getGuiPreferences(). The GuiPreferences object offers then methods to get and set values for this user.

The users list in the group management for example features much more data than is shown in the default view. However, presenting all data would make the table very large and would force you to scroll vertically. In this table a small icon right on top of the table lets you customize this tables view. Some other tables also use this feature, e.g. the listing of learning resources has an additional date field that is not visible in the default view.

Another place where the GUI perferences are used are the menu and toolbox state. Menus and toolboxes can be put into a dynamic / hidden state. This state information is stored in the GUI prefereces.

Another example is the home site portal configuration. The users personal settings are also stored in the GUI preferences.

Chapter 3. OLAT development framework overview

summary

Would you like a brief summary about how OLAT works in a technical way and how its architecture is? Here we are!

3.1. Architectural overview

Introduction

The OLAT Application is primarily a servlet application which simply needs a servlet container (e.g. tomcat) to run. It uses a lot of opensource frameworks to get its job done. The most wellknown are

  • hibernate for the o/r database mapping

  • velocity for the layouting process

  • spring for configuration issues

The GUI framework is a self-developed framework, which is component-based, follows the MVC (Model-View-Controller) paradigm, and focuses on reuse of component and also of workflows. It is a lot like Java Server Faces, but more powerful. Programming the GUI is very similar to developing with SWING or SWT. We have panels, Containers with layouting, pre-built components like links, forms, tables, and so on. The workflows are grouped into reusable controller classes, so that e.g. a user search (a search form, then a list of found matches) can be reused all over the places

The framework can support any language since it saves both localization and user data in utf-8. Even mixed content (content packaging with some encoding other than utf-8) works fine. There is an online translations tool within olat so that translating into a new language is a piece of cake (but still a lot of work!!) See Customizing

System boundaries

Programming concept

The OLAT programming concept can summarized as follows: Swing like - Component based (MVC), Business task (Controllers), Business logic (Managers), use of well established open source libraries

Here is a very general overview of OLAT and its concepts.

Component based

A Component is a visual representation of e.g. a Link, Form, a Table and so on. One ore more Controller can listen to Event which are fired when the user e.g. submits a form. Each component must provide its HTML-Renderer (method getHTMLRendererSingleton (interface ComponentRenderer) which know how to render its state in HTML.

Separation of logic and layout

Business logic is encapsulated in Controller classes (and the Manager classes they use), whereas the layout can be controlled by mainly modifying CSS. Almost all images can be configured using CSS also. In case you really would like to dig even more into the layout, you can easily change the HTML-Fragments which is HTML-Code with some Velocity commands in it

Event dispatching

The GUI Framework has mainly two things to do upon each browser-click from a user:

  1. Dispatch the request to the component which was clicked in the browser

    • find the component (with the component-id of the url) in the component tree of the contentpane of the window

    • call dispatchRequest(UserRequest ureq) so the component can adjust its state

    • the component will fire an event to the listening controllers if appropriate (e.g. "Treenode-clicked" with the id of the clicked node)

    • the controller(s) which receive the event update their business logic

    • as a result, the controller updates some of the components it owns (e.g. advance in a wizard step) and might fire an Event to listening "parent" controllers. (e.g. an UserChosenEvent

  2. Render the new state of the screen into HTML.

    Or deliver the Resource (see MediaResource Type and its implementations) to the browser if it is e.g. a PDF-Document or such (non-html)

    The rendering is done by accumulating the output of the components in a StringBuffer while traversing the whole component tree. At the end, the whole StringBuffer is sent to the client's webbrowser. This uses some temporary memory, but has the advantage that the user never sees those half-baken screen in case of an error. The page will either work completely or produce a decent error screen with a reference number for the support staff.

OLAT Web 2.0 Ajax mode

With the release 5.2.0 OLAT introduced a significant improvement in case of user experience and architectural change. The component architecture allowed us to easily switch to a mode where the components come in play up to the browser client.

In non ajax mode all components where rendered on the server and concatenated to a big html file and delivered to the client which rendered the whole page for each click. The ajax mode allows now to just send the components that are dirty to the client and the client renders the html fragments into the GUI by dom replacement. As we do this on component level the whole process is transparent for the developer and therefore very little special effort is needed by the developer to achive this.

To make sure everyone gets the best OLAT experience the ajax mode only gets automatically enabled for fully tested browsers. See the olat-config.xml file for a list of supported browsers. The not supported browser get just the normal request based full layout page with each click.

Features only avaiable in Ajax mode

The ajax mode has a polling feature where you can send periodically a request to the server to check for dirty components, that need to be rerendered on the user screen. This is e.g. used in the InstantMessagingMainController to inform users about new incomming messages even if they did not click in the OLAT GUI. See the JSAndCSSComponent constructor for an argument called pollintervall. If several pollivervalls are requested by controllers the server sets the poll intervall to the lowest available value which can be changed in the livetime of the controller by calling setPollIntervall. If you enabled the Instant Messaging feature you will have a polling channel for the whole GUI as the InstantMessagingMainController is always present in the upper right corner.

Technical backgrond of the OLAT ajax mode

In OLAT ajax mode all requests to the server are send to an hidden iframe target. The answer comes back to this target as well and is processed by javascript (see: a_invoke in functions.js file). The transport between the server and the client is handled by json a lightweight data-interchange format. Json was choosen to further minimize the amount of data that gets interchanged between the server and the client.

Listening to externel events in your controller

So you have your controller which handles you clicks and form events, but how can you listen to external events like an incoming instant message from an other server? There is a interface called GenericEventListener which you can implement for such purposes. This interface offers you an additional generic event method you can call yourself from outside your controller. See again the InstantMessagingMainController which has the generic event interface implemented. The generic event method for this controller gets called by the ClientManager which handles connections and message listeners for all the instant messaging clients and sends events to the contollers.

Programming for ajax mode

Basically nothing special has to be done to make your controllers work in ajax mode but by a good design you can increase the user experience a lot. E.g. while chatting, you may type and receive messages at the same time. With a good design you can decouple both actions so the user GUI gets updated while still typing. To do this you have to split you interface into panels which can be easily updated independently as a panel is a component which can be adressed and setted dirty. If you study the InstantMessagingMainController and the velocity files you will get an idea how to do this.

There are a few limitations that you have to keep in mind when programming for the ajax mode.

  • When opening a new window by a link, you should disable the ajax mode for this link otherwise the link gets posted to the background into the iframe. See Link component for how to disable the ajax mode for a single link.

If you like to debug the ajax mode to see what is sended between the server and the client use either firebug firfox extensio where you can capture the net traffic or the linux util tshark or wireshark which is a packetsniffer. In OLAT code have a look into the org.olat.core.gui.control.pushpoll package where you find the AjaxController or the JSServerController which server the json answer by calling the extractMediaResource method.

OLAT supported javascript libs

OLAT supports starting with the release 5.2 to support a few web 2.0 ajax libs which will be present for a longer time period, which means you can depend on and use it in your code.

  • Prototype from prototypejs.org . Prototype is a JavaScript Framework that aims to ease development of dynamic web applications.

  • script.aculo.us is build on top of prototype and delivers very nice effects for autocompletion and drag and drop. effects.js dragdrop.js controls.js

  • Extjs (Extend the web) is our rich client javascript Framework which offers very nice widgets like floating panels. Extjs is also build on top of prototype. Currently we only use tooltips and floating panels from the ext library. See the testing->GUI demo tab for examples of the tooltip stuff. If you want a nice floating panel like in the instant messaging you have to create instances of FloatingResizableDialogController

Ease of developing and debugging

OLAT helps the developer with several built-in features. These should be disabled in a productive environment.

  • enable the GUI debug mode by starting OLAT in debug mode. Adjust olat.properties file with "brasato.debug=true" and restart OLAT. You will find a red "bug"in the upper left corner of OLAT. By clicking it you get a new inline window which has several different debug modes. The debug button shows OLAT in a view where all the components and its nesting are visible. By hoovering over some parts you will get the corresponding Controller and it's Velocity file. The functionality should also always be enabled on the demo.olat.org website (Login as Adminstrator).

  • refresh localized strings

    this is enabled by setting

    localization.cache=true

    in the olat.properties.in

  • configure velocity engine to refresh files

    this is enabled by setting

    velocity.cache.pages=true

    in the olat.properties.in

  • online translation tool, see online translation tool

The GUI debug mode helps in understanding the GUI stack structure. It is then easy to find out why something does not show up where it was intended to, see Figure 3.2, “GUI debug stack” . Refreshing the velocity pages and the refreshing localized string properties enable the developer to change/fix broken layouts and translation keys on the fly. The yellow highlightend component, visible in Figure 3.3, “GUI debug listeners box” , produces a floating box with additional information:

  • type, the java class name of the component.

  • listeners, a list of event listeners attached.

  • cListeners, a list of controller event listeners

  • translator, shows where the keys come from and in which language. Clicking on the link brings up a new browser window with the possibility to interactively fix translation keys.

  • nesting level, a counter for nested div's tables respectively.

Figure 3.1. Select Debug Mode

Select Debug Mode


Figure 3.2. GUI debug stack

GUI debug stack


Figure 3.3. GUI debug listeners box

GUI debug listeners box


The OLAT framework works with the unchecked exception principle. This means that the developer should not care about exceptions in general. Exceptions go there long way up to the top level of OLAT, where they are catched and translated into a red screen, see Figure 3.4, “Red screen of an unchecked exception.”

In a productive environment a user only gets the Error code and the Date and time information, which she should present to the support. The system administrator can retrieve within OLAT the associated stacktraces and errormessages for further measures.

Figure 3.4. Red screen of an unchecked exception.

Red screen of an unchecked exception.


Authentication and Authorization

Authentication: Supported providers so far are: Normal formbase username/password login, and Shibboleth 2.x login and LDAP. See Classes of type AuthenticationController.

Authorization:

  • The basesecurity is a little like Java Security.

  • A policy protects olatresources allowing permissions (e.g. access, read, hasRole, etc.) to given securitygroups

  • No permissions to users, if they aren't in securitygroups.

  • Rights are positiv and additiv.

Instance rights versus type rights

If you have no type rights at all and someone assigns you as owner of a certain resource, you have instance rights for only this resource. Addionaly you can create other resources of this type. But if all resources to which you have instance rights are deleted, you are not more able to create one. In the opposite if you have type rights to a kind of resource, you always can create instances of this kind.

existing rights in the system:

       +---------------+
       | permission    |
       +---------------+
       | hasRole       |
       | access        |
       | admin         |
       | nologin       |
       | coach         |
       | participant   |
       | read          |
       | bgr.archive   |
       | bgr.assess    |
       | bgr.editor    |
       | bgr.groupmngt |
       +---------------+
      

How security is used explained by creating a new instance of a buddygroup

  1. create 2 security groups -> ownerGroup, -> partipiciantGroup

  2. create a buddyGroup with name, description, introMsg and the 2 security groups

  3. 2 policies, ownerGroup -> PERMISSION_ACCESS -> buddygroup. partipiciantGroup -> PERMISSION_READ -> buddygroup

Figure 3.5. creating a new instance of a buddygroup

creating a new instance of a buddygroup


GUI demo: Grafical elements in action

To get an overview and also have some good starting points for sample code login to OLAT as adminstrator and check if you see the main tab (GUI Demo). This tab shows you most of the visual elements of the OLAT framework in action. To access the sourcecode just click the view source code link (source shows you the java source and velocity the html fragments). If you cannot see the tab it is not enabled. To enable it go to te olat_extensions.xml file and uncomment the spring bean: olatsites_guidemo and restart OLAT. You can also debug the whole GUI by starting OLAT with the brasato.debug=true in the olat.properties file and restart OLAT. The green bug in the upper left corner lets you explore the GUI and lets you view the underling source by clicking the links. See also chapter:GUI debug stack.

For easy reference the OLAT demo server should also be setup with the settings above.

Proper usage of escaping in velocity pages

The (HTML) attribute values (see Velocity pages or in Renderers) could contain DB stored data (e.g. long course name becames an alt attribute) or a translated string (found in LocalString*.properties).

The HTML attribute values (in Velocity pages or in Renderers) must be HTML escaped (using org.apache.commons.lang.StringEscapeUtils.escapeHtml), else could break the javascript:

- use example in Velocity page: alt="$r.translateInAttribute("import.example")"
- use example in renderer: sb.append("\" title=\"" + StringEscapeUtils.escapeHtml(title) + "\">");

See org.apache.commons.lang.StringEscapeUtils and org.olat.core.gui.render.velocity.VelocityRenderDecorator.

We must avoid using HTML attribute values with single quotes, since it not possible to escape using the StringEscapeUtils methods. The translated strings used in non-attribute values doesn't need escapeHtml. (see GUIMessage.renderError) See also org.apache.commons.lang.StringEscapeUtils.escapeJavaScript.

3.2. Application Layers

Responsibilities of the layers in short

Servlets deployed by OLAT

The OLATServlet is the main entry point for user requests from the browser. This servlet uses Dispatchers to further process a request. This is done by the class DispatcherAction. Furthermore, the OLATServlet is responsible for initializing and configuring OLAT. This is done by calling the ConfigurationManager at startup.

DispatcherAction analyzes the first part of the incoming request URI and decides which Dispatcher is repsponsible for further dispatching the request. These Dispatchers are namely:

  • DMZDispatcher: Responsible for all requests of users who are not yet authenticated. DMZDispatcher processess all URIs starting with /dmz/, namely the login process.

  • AuthenticatedDispatcher: Responsible for all requests of users who have been previousely authenticated. AuthenticatedDispatcher will check each incoming request if it has an authenticated user associated. If not, the user is redirected to the login screen. AuthenticatedDispatcher catches all requests starting with /auth/.

  • ShibbolethDispatcher: This Dispatcher catches all requests comming asynchronuousely from IdentityProviders. Its assigned sub-URI is "/shib/", please refer to the Shibboleth Protocol for further infos on Shibboleth.

Besides OLATServlet, there are a few more servlets used within OLAT. Find a brief description of the functionality they provide below:

  • SecureWebdavServlet: This is the servlet handling all WebDAV requests. The servlet is mapped at URI /webdav/. This URI is the same for every user. The WebDAV contents are assembled for every user after authentication.

  • StaticsServlet: This servlet is handling requests for statics such as imges or CSS and Javascript files. StaticsServlet is mapped to the sub-URI /secstatic/. This sub-URI is further followed by a handler name (i.e. /secstatic/HANDLER/. According to the handler name, SecstaticsServlet passess the request to the handler which registered for the given handler name. Handlers can be configured in olat_config.xml. A handler must implements PathHandler and an alias must be given under which requests are dispatched. See the configuration of the StaticsModule in olat_config.xml namely its Path entries for an example.

  • RSSServlet: This servlet pretty much does what it says. It provides RSS feeds for subscribers and is mapped at /rss/.

  • OLATDevServlet: This servlet is used for development purposes. It implements workflows usefuly to the development of OLAT. With this servlet, developers can edit Velocity pages on the fly by enabling debugging in the GUI. Furthermore a translation tool helps in translating OLAT to other languages. The translation tool is accessible through the administration page.

Windows / Chiefcontrollers

Each User has a UserSession which stores the windows amongst other things. The Windows contains at least one Window. A window is the server-side representation of a browser window. A window has a content-pane (like Swing) to store the "real" content. Each window is owned by a ChiefController which is responsible for the navigation the windows offers, but not for its content. So e.g. after logging in, the tabs on the top ("home", "groups", ...) are controlled by the chiefcontroller.

Controllers

A Controller is a responsible for a certain business logic / workflow

Each controller must provide an initialComponent, which is the visual representation of the controller at any given time. The component will be rendered later into a HTML fragment which is part of the whole HTML page. You could think of a Swing Panel which is embedded into some place in the webbrowser window. The code creating a controller and asking for its initialComponent can determine where to visually put the component

Managers

Managers within OLAT, by definition, are classes that implement basic services useful for implementing the actual business logic. The OLATResourceManager for instance provides the basic functionality for handling OLATResources such as creating, reading, updating and deleting. Any Controller within OLAT that has to deal with an OLATResource should use the OLATResourceManager and not directly imlement basic functionality. In particular, Managers wrap all database access.

Virtual Filesystem (VFS)

OLAT provides its own implementation of a filesystem abstraction layer, called Virtual Filesystem (VFS). The VFS represents a tree structure with folders and files within folders much like a regular filesystem. Folders are called containers and files are called leaves. There are different implementations of containers and leaves serving different purposes. These offers the flexibility to change the implementaion of the file and folder classes and may switch to an other storage solution (like database) without rewriting code in the file handling classes.

Both VFSContainer and VFSLeaf inherit from VFSItem with generic attributes and methods common to both such as a name, methods to query wether delete or write is allowed and others. The most important method defined on VFSItem is resolve(String name). This method allows to query a VFSItem to resolve a path (i.e. /home/user/readme.txt). A path is much the same as a path on a regular filesystem. Resolve() returns a VFSItem which can be either a VFSContainer or a VFSLeaf.

The second most important feature of VFSItem is its VFSSecurityCallback. A security callback defines what operations are allowed on the item. VFSSecurityCallback defines whether read/write/list/copy and delete operations are allowed. If a specific item has no VFSSecurityCallback defined, all operations are allowed by default. VFSSecurityCallbacks get inherited by all children attached to an item. So for example, if a container has a security callback set that denies write operations, the container itself and all of its children would not be writable. VFSSecurityCallback also allows to define quotas on a object. The VFSSecurityCallback is checked by the VFS implementation. I.e. if you perform a write operation, the security callback is checked and an exception is thrown if the security callback denies write opertaions on the specific item.

There are various implementations of VFSContainer and VFSLeaf already in place. The most handy probably are LocalFolderImpl and LocalFileImpl which are an abstraction of a local filesystem. These are probably the place of start for all your VFS tasks. Simply create a LocalFolderImpl with a File pointing to a local folder as constructor asgument. You can then resolve any file or folder within this local folder through the resolve() method as described above. You can create new folder or files through the respective methods of VFSContainer (createChildContainer and createChildLeaf).

A special type of implementation are all implementations based on AbstractVirtualContainer. Those will be described briefly below.

  • NamedContainerImpl : This is useful if you want a given container to show up under a different name. Note that this name will also be used to resolve the container. This works much like a symlink on a regular fielsystem.

  • MergeSource : This is a powerful but maybe a bit difficult to understand type of virtual container. It is also the reason why we chose to do our own implementation of a virtual filesystem instead of using for example Apache commons VFS. A MergeSource combines several containers or leaves into a single container. To give you an equivalent on a local filesystem, you would create a new folder, and then symlink folders or files into this folder to get the same effect. A MergeSource can merge containers in two different ways provided by two different methods. addContainer will show the given container as a child of the MergeSource. addContainersChildren will show the given container's children as children of the MergeSource. To avoid naming conflicts, only one container can be addad via addContainersChildren . Furthermore you must define whether it is allowed to write to this container. If someone wants to write to a MergeSource, this is only possible, if a container was previousely added via addContainersChildren with the write flag set to true. This feature is manly used in the WebDavProvider to show folders of different courses a user is owner in one place which makes it handy to upload files for different ressources. This folders are spread on the local filesystem and the MergeSource allow us to see them in one place.

    See

  • StreamedImpl : This is useful, if you just want to decide on the fly, what the contents of a file is. StreamedImpl simply directs its InputStream and OutputStream to whatever you provide at construction time.

With these implementations, you are quite flexible in presenting a filesystem the way you want. Note that you may also combine different implementations of VFSContainer or VFSLeaf. The WebDAV implementation in OLAT makes use of most of the features provided by VFS. At its root there is a merge source. The merge source combines VFSContainers which themselves are wrapped by NamedContainerImpls. This way, the different types of directories exposed by WebDAV can be presented in a sensible way to the user.

See javadoc of the package org.olat.core.util.vfs to get some code examples on the general usage

See VFS javadoc for more information.

Database Abstraction Layer (Hibernate)

OLAT uses the hibernate relational persistence framework to map objects to relational database tables. See the hibernate documentation to learn how to use this framework.

Every persitable class in OLAT inherits from or extends org.olat.persistence.PersistentObject . This guarantees that every class has the following common attributes:

  • private Long key = null

  • private Date creationDate

  • private Date lastModified

To make a class persistable by the hibernate layer one needs to implement getter and setter methods for every attribute added to the class; this is a hibernate convention. Getters and setters can be private if they should not be used by code outside the class.

Every class must also have an interface if it wants to use the lazy initialization options. See the hibernate documentation for more information on this. These are the naming conventions when using interfaces:

  • Interface: ClassName.java

  • Implementation: ClassNameImpl.java

Every persistable class must have a hibernate mapping description. This mapping file (an XML file) must be stored in the same package as the mapped class. See the hibernate documentation to learn how to write the mapping file. There are several strategies how to write the mapping.

You should never access the OLAT database directly as you bypass hibernate and all its logic which could let to problems. To access the OLAT database you shoule always work with the provided manager classes. To get you an idea of what kind of manager there are available have a look at the relational database schema: OLAT Database Schema v6.2.0

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”)

3.4. Elements explained

Controllers

A controller is a reusable business-workflow (see also above 'Layers in short'). A controller has the following responsibilities:

  • create the initialcomponent upon construction

  • dispose the controller and subcontrollers upon a call of doDispose

A controller can use:

  • the Manager classes to do the work (database/filesystem)

  • other controllers to delegate tasks to (e.g. to to delegate a user search to the UserSearchController)

  • The method getWindowControl() to obtain the possiblity to send a info/warning/error msg to the screen and to push a new gui-layer above the current layers (kind of like a modal dialog, e.g. for a user search) and to pop the layer if a subcontroller's work is done.

Please see the javadoc of each controller to see what it is for. Briefly there are:

  • generic GUI-Controllers (Layout, Wizard)

  • generic Business-Controllers (e.g. UserSearchController, FolderRunController, ForumController)

  • specific Business-Controllers (PublishControllers)

See the java classes extending the Defaultcontroller class

Managers

Managers within OLAT are conceptually responsible for providing the basic functionality to the workflows (i.e. controllers implementing the actual workflows). Managers usually provide persistence functionality such as storage in the database or on the file system. In general, no workflow should directly deal with the filesystem or the database, it's all handled through a dedicated manager implementing this functionality.

i18n

We could have used ResourceBundle from the Java SDK, but you cannot clear the cache which is annoying for translation. Since we use java.util.Properties, the encoding is assumed to be iso_8859_1 (see java.util.Properties). However, if you use the online translation tool (Administration / Online translation tool) you can work with input in any encoding (just submit the form...)

The i18n system is described in a separate document "Understanding the Internationalization (I18n) and Translation System"

The browser always sends it page in utf-8. encodings from resources like html files from content packages are converted automatically

If The browser always sends it page in utf-8. encodings from resources like html files from content packages are converted automatically

Mappers

A Mapper is used to route a certain url directly to some place in code. This is useful if you want to e.g. deliver static resources.

  • user-local mappers: the url routing only applies to the current user

  • global mappers: the url routing applies to all users

Properties

Properties are used in very many places in OLAT und persisted in the table o_property. A property is a data container and has at least one name apart from its id and lastmodified date. Addionally it has several fields (category, floatvalue, stringvalue, textvalue, etc.) to store more information depending on the context it's used. If some of this fields are not used, they can be null. Creating a new property, you have to think about how to retrieve it. Adding several properties to a already used or new specified category, you can easily retrieve them.

How to create, retrieve and delete a property

PropertyManager pm = PropertyManager.getInstance();
p = pm.createPropertyInstance(null, null, null, "_o3_", "InfoMsg", null, null, null, "");
       
pm.saveProperty(p);
  
Property p = pm.findProperty(null, null, null, "_o3_", "InfoMsg");
       
pm.deleteProperty(p);

db

The DB is used as the main and only entry point for all database operations on the low level. It uses a threadlocal variable to keep the current hibernate session and the transaction state. At the end of each UserClick/Servlet Response, the session must be closed by using DB.getInstance().close(), before it can be returned into tomcat's threadpool!

Controllers should only use the beginTransaction(), commit() and some helper functions to reload an entry from disk if the associated session has been closed (e.g. transactions which would last several userclicks)

Manager classes should encapsulate business logic and thus be the only ones that use the find/save/update methods of the DB class

3.5. The util package

The org.olat.core.util package provides helper classes and their functionality used throughout the framework, the managers and the controllers. Following you'll find a brief outline of utility classes which might be helpful if you are developping with OLAT.

  • org.olat.core.util.bulk : Package which provides the abstract class BulkAction . Extend it when you have to handle a list of users with the same action. To understand how it's implemented have a look at the BulkAssessmentWizardController

  • org.olat.core.util.cache : Package which provides some implementation of caches used by the DB abstraction layer, the group management and the base security package. See the section on caches for details.

  • org.olat.core.util.component : Package which provides visitors and traversers

  • org.olat.core.util.controller : Provides everywhere useful controllers

  • org.olat.core.util.event : Package which provides a simple event handling system, described in more detail in the section on events.

  • org.olat.core.util.i18n : This package handles all i18n within OLAT. See the section on i18n for details.

  • org.olat.core.util.locks : Package which provides a manager to handle locks. A workflow may set/remove locks to prevent multiple users from accessing some functionality within OLAT. Locks are persisted accross sessions.

  • org.olat.core.util.mail : Contains Emailer , a class providing basic email functionality.

  • org.olat.core.util.memento : Provides the interface for the memento pattern

  • org.olat.core.util.nodes : Provides classes to represent a simple node structure and a helper class NodeHelper to manipulate such structures.

  • org.olat.core.util.prefs : Package whcih provides structures persistence classes for handling various persistent preferences.

  • org.olat.core.util.radeox : Package extending the Radeox wiki markup.

  • org.olat.core.util.rss : Helper classes for handling RSS data.

  • org.olat.core.util.traversal : Package providing helpder classes for traversing structures.

  • org.olat.util.tree : Package providing helpder classess to work on tree structures.

  • org.olat.util.vfs : Implementation of the virtual filesystem. See section on virtual filesystem for details.

  • org.olat.core.util.xml : Provides helper functionality to easy handle xml stuff

3.6. OLAT Clustering and Scalability Concepts

This chapter describes clustering and scalability concepts introduced to OLAT 6.1. These concepts help to distribute OLAT over several tomcats with a load balancer in front. It is not designed for high availability, sessions on a node which goes down are kicked out. But it allows to go beyond the 2-3GB JVM RAM boundary by running multiple tomcats each with 2.5GB JVM RAM assigned.

Note that OLAT can either be run in singleVM or in cluster mode. Nevertheless the developer needs to know about clustering mechanisms even if he or she would not run an OLAT in cluster mode.

Hence when you use Caches, EventBus or any type of locking or synchronization read this section carefully to ensure that your code works in both singleVM and cluster mode deployments

When resources are used from within a single Java VM and are to be protected by serializing access to them, the synchronized keyword is used or the classes from the java.util.concurrent package. Since all calls go through the same JVM, this works fine.

When running OLAT in cluster mode (see chapter about the OLAT cluster installation), then several JVM share access to the same database, the same filesystem, and the same Instant messaging server.

It is of course necessary to protect the access to those resources that are shared between JVMs in order to guarantee a consistent and stable system.

Since a developer should not have to care whether or not its OLAT is running in singleVM or cluster mode, several „infrastructure tools“ (java interfaces) have been introduced to alleviate the life of a developer.

All those tools can be accessed through the Coordinator, which can be obtained via

CoordinatorManager.getInstance()

The four tools provided are:

  • Syncer : Synchronize access to OLAT-Resourceables

  • Locker: Acquire and release Locks, mainly for GUI-Locking

  • Cacher: a hierachical cluster-capable CacheManager

  • EventBus: for sending messages on channels and distributed cluster-wide

The next section explains what those tools are for

Syncer

The Syncer allows cluster-wide synchronization on a given OlatResourceable. That means that for each given OlatResourceable, at most one thread of all JVMs executes the „execute“ method of the provided SyncerCallback or SyncerExecutor concurrently.

public <T> T doInSync(OLATResourceable ores, SyncerCallback<T> action);

if you don't need a return object, then use the SyncerExecutor instead

public void doInSync(OLATResourceable ores, SyncerExecutor action);

An OlatResourceable is considered to be equal to another when both its type (a string) and its Id (a Long) are equal.

PS: In the current Syncer implementation the DB transaction is committed before doInSync returns. That means that you will have multiple commits during the handling of a request

PPS: In the current Syncer implementation doing so-called nested-doInSyncs is not allowed. Nested doInSync is when you are within a doInSync and call doInSync again. This will throw an Exception. The reason for not allowing this is to avoid potential, distributed deadlocks.

Implementation detail

In SingleVM mode, the OlatResourceable is converted to a String and then kept in a Map to serve as a monitor for the synchronized command.

public void doInSync(OLATResourceable ores, SyncerExecutor e) {
	synchronized (DerivedStringSyncer.getInstance().getSynchLockFor(ores)) {
		e.execute();
	}
}

In Cluster mode, first the same as in the SingleVM is done to limit concurrent select-for-update tries to one per JVM . Then the pessimistcLockManager is used, which obtains a write lock on the row in table Plock representing the olatresourceable. if the olatresourceable does not yet exist in the table, it is created first using another select for update on a row that is guaranteed to exist on system startup. (a global lock)

public void doInSync(OLATResourceable ores, SyncerExecutor e) {
	String asset = OresHelper.createStringRepresenting(ores, null);
	synchronized(DerivedStringSyncer.getInstance().getSynchLockFor(ores)){
		DBFactory.getInstance().beginTransaction();
		// acquire a db lock with select for update which blocks other
 		// db select for updates on the same record 
		// until the transaction is committed or rollbacked
		PessimisticLockManager.getInstance().findOrPersistPLock(asset);
		e.execute();
		// now release the lock - only if previously no other
 		// transaction was spawned.
		// commits only if the beginTransaction above was the first
 		// nested - otherwise the commit reaching level floor will 
		// actually commit it.
		// the transaction bracket is opened and closed here so that
 		// also non-db synchronized actions such as file system 
		// operation can be safely synced.
		DBFactory.getInstance().commit();
	}
}

Locker

This is the replacement for the old LockManager class .

It allows to obtain a lock on a given OlatResourceable.

Both non-persistent and persistent Locks can be aquired.

A non-persistent Lock is cleared when either the JVM crashes or (more often hopefully ;)) when the user that is the owner of a Lock logs off or when the controller that technically holds the lock releases its lock, e.g. in the dispose() method. Non-persistent locks are used for GUI-Locking.

A persistent Lock survives JVM startup/shutdowns and is used to protect access to resources where an explicit release of the lock is needed. (e.g. when editing a QTI document)

Try to avoid persistent locks in favor of non-persistent locks.

Implementation detail

non-persistent locks:

  • In SingleVM mode, a lock is essentially a LockObject held in a Map of the SingleVMCacher singleton.

  • In Cluster mode, the lock is written to the database to be cluster-capable.

The persisting locks are backup by a database table for both the singleVM and the cluster mode.

Cacher

The Cacher is a hierachical cache structure to cache data

If, for example, you would like to obtain a cache per identity per course (to e.g. be able to cache the assessment data for the user), then you must build the hierarchy as depicted above. The CourseEnvironment creates an AssessmentManager instance per Course which then obtains a CacheWrapper from the Cacher, and e.g. will create child CacheWrappers for its Course and request CacheWrappers for each identity of this course (normally lazily, that is only when data needs to be read). For each level the ehcache configuration is read from the olatcoreconfig.xml (e.g. a „Course“ entry is then applied to all child CacheWrappers which are created with an OLATResourceable which is of type „Course“)

Configuration parameters are:

  • max time to live

  • max time to idle

  • max elements

See also the Hints and cookbook section.

The hierarchy is only used for namespacing reasons. To e.g. obtain a cache for a certain user in a certain course, it is recommended that the responsible manager class holds a root-CacheWrapper and then for each course it creates a child CacheWrapper which in turn is used to create a CacheWrapper per user by using the appropriate method on the course CacheWrapper. The actual cached data for an identity resides in the CacheWrapper obtained for the Identity.

PS: Make sure to not store objects retrieved from the Cacher in instance variables. Otherwise an object might have been replaced with a newer version in the Cacher yet you don't notice as you're still holding the old object. Also it would violate the idea that the Cacher can control how many objects are kept in memory (memory management)

PPS: Note that there is currently an issue (OLAT-4424) where you can potentially run into a nested doInSync exception due to a cached object having to be reloaded

Implementation detail

The CacheWrapper class in OLAT is mainly a wrapper around the EHCache from the ehcache library.

The Cluster mode version is the same as the SingleVM version, except that it invalidates elements that were „put“ into or „removed“ from a cache by sending an ClusterCacheWrapperEvent to all other cluster nodes.

EventBus

The EventBus is used to broadcast messages which normally affect more than one user.

It offers channels (identified by a OLATResourceable) to separate between different topics of interests. Code can subscribe/register and unsubscribe/deregister.

If your code subscribes to the channel, it is also responsible to unsubscribe at the end of your object's lifecycle (e.g. in the dispose() method of your controller).

The event bus has a topic or publish/subscribe architecture – that is the sender of a message does not know

  • if there is anybody listening at all on a particular channel

  • when its message will arrive

The recepient on the other side should:

  • only take a short time to do its tasks

  • only invalidate data, not reload it (we can postpone that before it is rendered, if it even gets rendered at all)

  • do not synchronize on data (possibility of deadlocking)

  • know which channels it is interested in and only register to those

Implementation detail

In SingleVM mode, the event bus is mainly a Map per OLATResourceable which implements a listener/sender (observer) pattern.

In cluster mode, the JMS provider is used to serialize those messages and send them asynchronously to all cluster nodes. The messages to its own JVM are sent „inhouse“, that is, using a direct method call (this is more efficient and allows for one node to still run if all other nodes and the JMS bus is down).

Example usages of the tools

Serializing access to the database for special operations

Description of the problem:

Enrollment can be configured to only accept a certain number of people in a group.

When two users subscribe to the same group at the same moment with only only place being left, only one user can be accepted, and the other one must be rejected.

Solution: the following operations need to be performed and need to be serialized.

  • read how many users are already in a group

  • if the maximum is not reached yet, insert the user into the group

The 2nd point happens by adding an entry in the relationship table between identities and groups.

A common read_committed isolation is not enough here, since the database does not offer a constraint such as „the number of entries in the n-n table must not exceed the number which is described in the group“

How it was solved in SingleVM OLAT: with synchronized(olatresourceablestring) {...}

How it is solved in cluster mode olat: the Syncer uses a select for update on a special olat database table.

What effectively happens on the database in cluster mode is the following: (SQL is in pseudocode)

Table 3.3. 

Thread 1Thread 2
select for update from plock where asset=“businessgroup:12345“ select for update where asset=“businessgroup:12345“
select count(*) from group g, gr2ident g2i identity i where g2i.group = g and g2i.identity = i and g.id = xx - assuming thread 1 acquired the pessimistic lock before thread 2, then thread 2 has zu wait till thread 1 either rollbacks or commits.
if (count < allowed max) -> insert new tupel into gr2ident table (=adding a user to this group), else send message to the user's screen.  
commit the transaction thread 2 now gets the lock on the row with asset=“businessgroup:12345“ and continues with the select count(*)... as shown in thread 1

Things to avoid

Next is a list of things to avoid in coding, please add more to it.

  • Using ehcache's classes directly: always use Cacher interface, (or otherwise improve it)

  • Calling doInSync from within doInSync - aka nested doInSync - this causes an Exception to be thrown in the current Syncer implementation

  • Using too many doInSyncs or using doInSyncs frequently with the same OlatResourcable. Note that doInSync can become a bottleneck if many users/requests want to synchronize on the same OlatResourcable. Therefore try to use an OlatResourcable which is as specific as possible (hence can be potentially used in parallel by as few as possible users/requests).

  • Doing expensive operations within a doInSync. Remember that within a doInSync you are holding a cluster-wide synchronization lock and that you prevent any other user/request from calling into the doInSync with the same OlatResourcable. Therefore time spent within doInSync is very precious and should not be wasted. Do as few things in doInSync as possible.

  • Using Cacher.put when you actually wanted to have other cluster nodes be informed - use update/updateMulti instead

  • The above is true the other way round as well: Try to use Cacher.put where possible - as it doesn't require other Caches to be informed.

  • Keeping a reference to an object retrieved from the Cacher in an instance variable. Fetch the object from the cache instead of storing it somewhere in a field

  • Synchronous event calls: by design the EventBus is asynchronous. This is a good thing as it eliminates a potential performance problem whereby a thread needs to wait for a reply to its request. Always try to stay on the asynchronous side when using EventBus and don't implement anything synchronous.

Hints and cookbooks for the four „tools“

We describe a few tips, hints, and a kind-of-a-cookbook for the four tools explained in the previous chapter.

Cacher

Think about caching in general before using the cacher

  • when to use it („prove“ a cache is needed (you can use a cache in code, but set maxelements to 0 if not needed)

  • calculate the size per element to estimate memory usage

  • the single element to cache should have a certain size (tara/netto, payload)

put versus update versus updateMulti:

be vary about methods offered: in cacher, there are three ways of storing something in the cache

  • put: This puts a key/value pair into the cache - without notifying any other cluster cache (if in cluster mode).

  • update: This puts a key/value pair into the cache - notifying any other cluster cache of the change (if in cluster mode).

  • updateMulti: Same as update but with multiple key/value pairs. This is speed optimized compared to calling update n times!

Cache configuration:

in the file olatcoreconfig.xml there is a bean named org.olat.core.util.cache.n.impl.cluster.ClusterCacher and in the single JVM mode the bean is in the same file, but named org.olat.core.util.cache.n.impl.svm.SingleVMCacher The property „rootConfig“ with its children config describes how the caches are configured. The sample below means that the NewCachePersistingAssessmentManager has a cache to create subcaches per course (entry with key „CourseModule“) and from this cache a cache per identity (entry with key „Identity“ in this course is created. How does this now look from the Java side? In the class NewCachePersistingAssessmentManager, there is one static field

private static CacheWrapper assessmentMainCache = CoordinatorManager.getCoordinator().getCacher().getOrCreateCache(NewCachePersistingAssessmentManager.class, null);

when an instance is created per course, then also a cache is created.

private NewCachePersistingAssessmentManager(ICourse course) {
  this.course = course;
  courseCache = assessmentMainCache.getOrCreateChildCacheWrapper(course);
}

this cache per course („coursecache“) is now used not for caching, but only to create subcaches for each Identity in the course.

private CacheWrapper getCacheWrapperFor(Identity identity) {
  OLATResourceable ores = OresHelper.createOLATResourceableInstanceWithoutCheck("Identity", identity.getKey());
  CacheWrapper cw = courseCache.getOrCreateChildCacheWrapper(ores);
  return cw;
}

The sample configuration:

<bean class="org.olat.core.util.cache.n.impl.cluster.ClusterCacher" ...>
...
<property name="rootConfig">
 <bean class="org.olat.core.util.cache.n.CacheConfig">
   <property name="childrenConfig"><map>
    <entry key="org.olat.course.assessment.NewCachePersistingAssessmentManager">
     <bean class="org.olat.core.util.cache.n.CacheConfig">
	<property name="timeToLive" value="1" />
	<property name="timeToIdle" value="1" />
	<property name="maxElementsInMemory" value="1" />	
	<property name="childrenConfig"><map>
	 <entry key="CourseModule">
	  <bean class="org.olat.core.util.cache.n.CacheConfig">				   
	  <property name="childrenConfig"><map>
	    <entry key="Identity">
	     <bean class="org.olat.core.util.cache.n.CacheConfig">
	      <property name="timeToLive" value="0" />
		<property name="timeToIdle" value="60" />
		<property name="maxElementsInMemory" value="1000" />
	     </bean>
    	    </entry>
	   </map></property>
	</bean>
...

If no configuration for a certain class is given, then, upon request of such a cache, the configuration of its parent is taken (which again may be inherited from its parents)

In previous OLATs, configurations from the cache were either coming from the file ehcache.xml (now deprecated) or were hardcoded in code.

Configuration parameters:

Table 3.4. 

parameterEquivalent in EhcacheDescription
timeToLive timeToLiveSeconds From the ehcache docu: Sets the time to live for an element before it expires. i.e. The maximum time between creation time and when an element expires. Is only used if the element is not eternal. Optional attribute. A value of 0 means that and Element can live for infinity. The default value is 0.
timeToIdle timeToIdleSeconds From the ehcache docu: Sets the time to idle for an element before it expires. i.e. The maximum amount of time between accesses before an element expires. Is only used if the element is not eternal. Optional attribute. A value of 0 means that an Element can idle for infinity. The default value is 0.
maxElementsInMemory maxElementsInMemory From the ehcache docu: Sets the maximum number of objects that will be created in memory The Ehcache within the OLAT CacheWrapper is only used as a RAM-Cache and never written to disk (overflowToDisk=false, diskPersistent=false)

The Ehcache within the OLAT CacheWrapper is only used as a RAM-Cache and never written to disk (overflowToDisk=false, diskPersistent=false)

Syncer

  • Don't simply replace all „synchronized“ with CoordinatorManager.getCoordinator().getSyncer().doInSync(...); everywhere!

    • Only those which have synchronized resources which -would- affect the state of several JVM when in cluster mode.

    • Not those which synchronize on any GUI operation or similar, e.g. within usersession or such.

  • The Syncer will -always- be used when synchronizing within manager classes.

  • Try to make the synchronized code block as small and as fast as possible

    • make sure that a sync'ed block never takes longer than at most 10 seconds or such (special solutions are thus needed for gzipping large data directories), otherwise the database will throw a LockWaitTimeoutExceeded Exception.

  • For each synchronized keyword you introduce, explain that and why it does not have to be a doInSync. This can be done via

    //o_clusterOK by:<person>

  • Make sure to use GUILocks where that would be a better fit than doInSync!

EventBus

  • if possible, take an already existing and generic MultiUserEvent

  • a Multiuserevent must be serializable and should be small (network traffic) -> preferable only use base java types and list or sets of base types.

  • only send one multiuserevent within one call (at least from within one manager)

  • calls to the event bus should only happen from Manager classes (with register helper method incoming calls are then sent back to the controller)

Locker

Try to rather avoid than to solve conflicts by using GUI-Locks with the Locker facility. There are many examples already in OLAT.

3.7. Going deeper into OLAT development

You will find more information about the design details in the OLAT Javadoc. Use this link as an entry-point to the Javadoc : OLAT javadoc

3.8. Reusable components and controller library

In this chapter we discuss some of the framework elements that you can use to build your code. Specifically we describe how to use some of the GUI components and reusable controllers.

Note that since this documentation is work in progress, not all available compontens or controllers are described. Since this is a new section in the documentation it will take some time to describe the older components as well. Your help is apprechiated, please help us making the documenation complete by writing articles about components!

TextComponent and TextFactory

Whenever you have a little text fragment that you want to display on screen you can use the TextComponent .

The following features are supported:

  • Display raw text (provide a string)

  • Display i18nified text (provide an i18n key and a translator. Translation happens at render time)

  • Wrapp in a DIV or SPAN element

  • Style by providing an optional CSS class name that is added to the wrapper DOM element

When the text (raw or i18n) and CSS class changed on an instantiated object using the appropriate setter method the text element will be redrawn on screen. This means that when you put a TextComponent in a VelocityContainer , you can change the displayed text without making the container dirty. Only the text component itself will be redrawn. This has many advantages when building a commplex GUI and you don't want to redraw everything just because a litte text element has changed.

Usage examples can be found in the GuiDemoLinksController . To create a TextComponent object, use the TextFactory . There are two methods:

  • createTextComponentFromString

  • createTextComponentFromI18nKey

Chapter 4. OLAT software license

See OLAT Software License for details of the Open Source license.

Chapter 5. Version Control and Branching

Here is a picture to give you a better idea of how version control and branching works in OLAT:

5.1. Feature branch

The OLAT source code is held in a CVS repository. The main development is done on the HEAD stream. For some extensive features, however, a feature branch can be created. It is merged back into HEAD after completion.

Feature branches must be named using the following scheme:

  1. Application name: OLAT

  2. Source branch: HEAD or name of a release branch, e.g. 6_1

  3. Owner: UZH, FRENTIX, BPS... (your name or company here)

  4. Jira Issue that describes the feature you implement: 1234. See the olat Jira server

Example: OLAT-HEAD-FRENTIX-3674, OLAT-HEAD-UZH-2385...

During the development phase, the branch is updated on a regular base from the source branch (normally HEAD). Do never merge something from a feature branch back to the HEAD unless your feature is finished. Only one back-merge is possible!

When updating your feature branch from HEAD, make sure you follow step-by-step this feature branch merge tutorial!

5.2. Release branch

Moreover, there is a release branch that holds the former and current releases. The idea of a release branch is that after releasing the HEAD is opened again for unstable development activities. A release branch however should always be stable and is only used to do important bug fixing on the release.

Release branches must be named using the following scheme:

  1. Application name: OLAT

  2. Release base: 6_1_x

  3. Indication that this is a branch: BRANCH

Example: OLAT-6_0_x-BRANCH, OLAT-6_1_x-BRANCH...

We do not make release branches for every single release. We maintain a release branch for each feature release. (6.0, 6.1 etc). The bugfix releases (6.0.1, 6.0.2 etc) are tags on those release branches.

As a developer one must take care of merging bugfixes back to the HEAD development. Each developer is responsible to do this. Best is to do it immediately after committing a fix to the release branch.

Chapter 6. Contact, mailinglists and support

6.1. Mailinglists

If you have any questions about the code or the usage of OLAT please contact us using one of our mailinglists. You will find them at http://lists.olat.org

Please follow the usual netiquette on mailing lists. The more detailed, polite and patient you are, the faster and better is the answer you will receive.

For personal questions you can contact us via info@olat.org. Please do not ask questions on this address that are of general interest, use the mailinglists for this instead.

6.2. Support

For professional OLAT support, hosting, training (developers, administrators or authors) or customization please visit the homepage of frentix.

Frentix GmbH is a spin-off company from the University of Zurich that has great experience in developing and deploying the LMS OLAT.