A Sakai tool typically consists of multiple Maven modules:
tool-name/
├── api/ # Service interfaces and models
├── impl/ # Service implementations
├── hbm/ # Hibernate mappings (if needed)
├── tool/ # Web application (UI)
└── pom.xml # Parent POM
Module Responsibilities
api : Defines service interfaces, data models, and constants
impl : Contains service implementations and business logic
hbm : Hibernate mapping files for database entities
tool : Web UI layer (JSF, Wicket, ThymeLeaf, or web components)
Technology Stack
Backend Frameworks
Sakai uses multiple Java frameworks from different development eras:
Spring Framework Dependency injection, MVC, and core services. Crucial for all development.
Hibernate ORM Database interactions and persistence. Critical for data access.
ThymeLeaf Preferred template engine for new tool development.
JSF 2.3 JavaServer Faces - used in many existing tools.
For new tools, prefer Spring MVC/Boot with Hibernate and ThymeLeaf .
Legacy Frameworks
Apache Velocity : Used in older parts of the codebase
Wicket : Component-based web framework used in several tools
RSF (Reasonable Server Faces) : Avoid for new development
UI Framework
Bootstrap 5.2 : Preferred UI framework for styling
Responsive Design : All UI must work across different screen sizes
Web Components : Strategic direction for frontend (see Web Components Guide )
Kernel Services
The Kernel provides core services that all tools should use:
Service Location
Access Kernel services using the Component Manager:
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.user.api.UserDirectoryService;
// Get service instance
UserDirectoryService userService =
(UserDirectoryService) ComponentManager . get ( UserDirectoryService . class );
Core Services
Services for fetching and managing User objects: import org.sakaiproject.user.api.UserDirectoryService;
import org.sakaiproject.user.api.User;
UserDirectoryService userService =
(UserDirectoryService) ComponentManager . get ( UserDirectoryService . class );
User currentUser = userService . getCurrentUser ();
String userId = currentUser . getId ();
String displayName = currentUser . getDisplayName ();
Handle user sessions: import org.sakaiproject.tool.api.SessionManager;
import org.sakaiproject.tool.api.Session;
SessionManager sessionManager =
(SessionManager) ComponentManager . get ( SessionManager . class );
Session session = sessionManager . getCurrentSession ();
String sessionId = session . getId ();
Check permissions and security: import org.sakaiproject.authz.api.SecurityService;
SecurityService securityService =
(SecurityService) ComponentManager . get ( SecurityService . class );
boolean canEdit = securityService . unlock (
"site.upd" , "/site/" + siteId
);
Send emails through Sakai: import org.sakaiproject.email.api.EmailService;
EmailService emailService =
(EmailService) ComponentManager . get ( EmailService . class );
emailService . send (
"[email protected] " ,
"[email protected] " ,
"Subject" ,
"Body" ,
null , // headers
null , // attachments
null // additional recipients
);
File and resource management: import org.sakaiproject.content.api.ContentHostingService;
import org.sakaiproject.content.api.ContentResource;
ContentHostingService contentService =
(ContentHostingService) ComponentManager . get ( ContentHostingService . class );
ContentResource resource = contentService . getResource (resourceId);
byte [] content = resource . getContent ();
New core services should be added to the Kernel , not to individual tools.
Create Maven module structure
mkdir -p my-tool/{api,impl,tool}/src/main/java
mkdir -p my-tool/tool/src/main/webapp
Create parent POM
Create my-tool/pom.xml: < project >
< modelVersion > 4.0.0 </ modelVersion >
< parent >
< groupId > org.sakaiproject </ groupId >
< artifactId > master </ artifactId >
< version > 26-SNAPSHOT </ version >
</ parent >
< groupId > org.sakaiproject.mytool </ groupId >
< artifactId > mytool-base </ artifactId >
< packaging > pom </ packaging >
< modules >
< module > api </ module >
< module > impl </ module >
< module > tool </ module >
</ modules >
</ project >
Define API interfaces
Create service interfaces in the api module: package org.sakaiproject.mytool.api;
import java.util.List;
public interface MyToolService {
List < String > getData ( String siteId );
void saveData ( String siteId , String data );
}
Implement service logic
Create service implementation in impl: package org.sakaiproject.mytool.impl;
import org.sakaiproject.mytool.api.MyToolService;
import org.springframework.stereotype.Service;
@ Service
public class MyToolServiceImpl implements MyToolService {
@ Override
public List < String > getData ( String siteId ) {
// Implementation
return new ArrayList <>();
}
@ Override
public void saveData ( String siteId , String data ) {
// Implementation
}
}
Create Spring configuration
Create impl/src/main/resources/spring-config.xml: <? xml version = "1.0" encoding = "UTF-8" ?>
< beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:context = "http://www.springframework.org/schema/context" >
< context:component-scan base-package = "org.sakaiproject.mytool.impl" />
</ beans >
Build UI layer
Create controllers and templates in the tool module using your chosen framework (ThymeLeaf, JSF, etc.).
Code Style Guidelines
Java Coding Standards
No var keyword : Do not use local variable type inference. Always declare explicit types.// CORRECT
List < String > names = new ArrayList <>();
// WRONG - Will fail Checkstyle
var names = new ArrayList < String >();
This is enforced by Checkstyle during mvn validate.
General Guidelines
Indentation : Maintain consistent format (tabs/spaces) as in existing files
kebab-case : Use for HTML class and id attributes
Internationalization : All user-facing strings must support i18n
Accessibility : Follow WCAG 2.0 AA standards
Minimal Changes : Only modify lines needed for your fix/feature
JavaScript Standards
Modern JavaScript : Target ES2022+ features
No jQuery : Use modern DOM APIs instead
Modular Code : Use ES modules and Lit components
No Global Variables : Prefer module scope or class fields
Database Integration
Hibernate Entities
Create JPA entities with Hibernate:
import javax.persistence. * ;
import lombok.Data;
@ Entity
@ Table ( name = "MY_TOOL_DATA" )
@ Data
public class MyToolData {
@ Id
@ GeneratedValue ( strategy = GenerationType . AUTO )
@ Column ( name = "ID" )
private Long id ;
@ Column ( name = "SITE_ID" , nullable = false )
private String siteId ;
@ Column ( name = "DATA_VALUE" )
private String dataValue ;
@ Temporal ( TemporalType . TIMESTAMP )
@ Column ( name = "CREATED_DATE" )
private Date createdDate ;
}
Repository Pattern
import org.springframework.data.jpa.repository.JpaRepository;
public interface MyToolDataRepository
extends JpaRepository < MyToolData , Long > {
List < MyToolData > findBySiteId ( String siteId );
}
Register your tool in tool/src/webapp/tools/sakai.mytool.xml:
<? xml version = "1.0" ?>
< registration >
< tool
id = "sakai.mytool"
title = "My Tool"
description = "Description of my tool" >
< configuration name = "home" value = "/" />
< configuration name = "reset.button" value = "true" />
< category name = "course" />
< category name = "project" />
</ tool >
</ registration >
Internationalization
Create resource bundles for multiple languages:
# mytool.properties (English)
mytool.title =My Tool
mytool.save =Save
mytool.cancel =Cancel
# mytool_es_ES.properties (Spanish)
mytool.title =Mi Herramienta
mytool.save =Guardar
mytool.cancel =Cancelar
Access in Java:
ResourceBundle rb = ResourceBundle . getBundle ( "mytool" , locale);
String title = rb . getString ( "mytool.title" );
Next Steps
Web Components Build modern UI with Lit-based components
Testing Guide Write comprehensive tests for your tool