Skip to main content

Tool Structure

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.

Creating a New Tool

1

Create Maven module structure

mkdir -p my-tool/{api,impl,tool}/src/main/java
mkdir -p my-tool/tool/src/main/webapp
2

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>
3

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);
}
4

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
    }
}
5

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>
6

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);
}

Tool Registration

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

Build docs developers (and LLMs) love