Addon development

This page describes a tutorial for create an addon into Fujitsu HPC Gateway.
To develop an addon, you have to known:

  1. Java
  2. Javascript
  3. ExtJS


PDF version: addon developper guide
Source code: sources
Addon zip file: addon




UUID Generator

Into Torii, open the Addon Management tool and generate a unique UUID with the Torii UUID Generator:


JS skeleton

Create a folder named my_addon_d9b1eedb in /Product/Addons/Fujitsu/

  • Create inside the file build.gradle containing:
description = 'my_addon_d9b1eedb'
toriiVersion = rootProject.properties.get('toriiVersion')

apply plugin: 'java'

repositories {
    jcenter()
    mavenCentral()
    mavenLocal()
    maven {
        url project.properties.get('gatewayRepository.libs')
    }
}

dependencies {
    compile group: 'com.fujitsu.fse.torii',  name: 'torii-rest', version: toriiVersion
}

sourceSets {
    main {
        java {
            srcDir 'Sources/backend/src/main/java'
        }
    }

    test {
        java {
            srcDir 'Sources/backend/src/test/java'
        }
    }
}

task zip(dependsOn: 'jar', type: Zip){
    outputs.upToDateWhen { false }

    from (jar.outputs.files) {
        into 'jars'
    }
    from fileTree('Sources/frontend/') {
        include 'js/'
        include 'resources/'
        include 'info.json'
    }
}
  • Create the file info.json in the folder /Product/Addons/Fujitsu/my_addon_d9b1eedb/Sources/frontend containing:
{
  "creationDate": {"$numberLong": "1439200457"},
  "modificationDate": {"$numberLong": "1439200457"},
  "name": "My Addon",
  "usualName": "my_addon",
  "uuid": "d9b1eedb",
  "version": "Develop-SNAPSHOT",
  "developer": {
    "firstName": "Christophe",
    "lastName": "Forcinal",
    "email": "christophe.forcinal@ts.fujitsu.com"
  },
  "controllersPath": ["MyAddonController"],
  "moduleEntryPath": "view.MyAddonModule",
  "services": {
    "packageName": "com.fujitsu.fse.torii",
    "names": [
   "MyAddonService"
    ]
  },
  "description": "Addon for the tuto.",
  "tags" : [
    "Tuto",
    "Test"
  ],
  "wikiURI": "http://hpcgdemo.fujitsu.fr:8082/dokuwiki/doku.php?id=1/addon"
}
  • Always inside the new folder, create the sub-folders:
    - frontend/js/controller
    - frontend/js/model
    - frontend/js/store
    - frontend/js/view
    - frontend/resources/css
    - frontend/resources/images
    - frontend/resources/locale-bundles

    This is the skeleton of our addon front-end side. You should have this:

  • Add an icon named icon.png in the resources/images folder. For the tutorial, we add the image:
    The icon must be named icon.png and be present in resources/images folder to works with the addon mechanism. No limit for the size.


Window creation

Create a file named MyAddonModule.js in /js/view containing :

/*global Ext,desktop */
/**
 * <h1>My Addon Window</h1>
 * Window of my addon
 * <p>
 * <b>Note:</b> Giving proper comments in your program makes it more
 * user friendly and it is assumed as a high quality code.
 *
 * @author Fujitsu Systems Europe
 * @version 1.0
 * @since 05/07/16
 */
Ext.define('desktop.addons.my_addon_d9b1eedb.view.MyAddonModule', {
    extend: 'desktop.core.utils.Module',
    requires:[],
    /**
     * Create window
     * @param taskBar the task bar
     * @returns {desktop.core.view.utils.ModuleView} the module view
     */
    createWindow: function () {
   var me = this;
        return Ext.create('desktop.core.view.utils.ModuleView', {
            title: me.title,
            iconCls: me.iconCls,
            name: 'my-addon-window',
            taskBar: me.taskBar,
            multipleView: false,
            items:me.buildItems()
        });
    },

    /**
     * Build items
     * @returns {{xtype: string, layout: {type: string, align: string}, items: *[]}} items in json format
     */
    buildItems:function(){
        return {};
    }
});

Language file creation

Create a file named my_addon_d9b1eedb.properties and my_addon_d9b1eedb_en.properties in /resources/locale-bundles containing:

my_addon_d9b1eedb.view.window.title=My Addon

The my_addon_d9b1eedb.properties file can be a copy of the my_addon_d9b1eedb_en.properties . It's the default file language. All properties must be prefixed by my_addon_d9b1eedb .


Controller creation

Create a file named MyAddonController.js in /js/controller containing:

/*global,Ext,desktop*/
/**
 *
 * <h1>My Addon Controller</h1>
 * This is the controller of my addon
 *
 * @author Fujitsu Systems Europe
 * @version 1.0
 * @since 05/07/16
 */
Ext.define('desktop.addons.my_addon_d9b1eedb.controller.MyAddonController', {
    extend: 'desktop.core.controller.abstract.AbstractToriiController',
    requires: [],
    control: {}
});


  • Edit the file settings.gradle to add the new addon project:
include ':my_addon_d9b1eedb'
project(':my_addon_d9b1eedb').projectDir = new File('./Product/Addons/Fujitsu/my_addon_d9b1eedb')
  • (Via Idea) In Gradle Project, now you have the new entry my_addon_d9b1eedb, then launch the Gradle command ToriiAddon/my_addon_d9b1eedb/Tasks/otherzip
  • (Via Terminal) In /ToriiAddon/ :
./gradlew :my_addon_d9b1eedb:zip

A zip my_addon_d9b1eedb.zip is now present in /Product/Addons/Fujitsu/my_addon_d9b1eedb/build/distribution



  • In the Addon Management tool, right clic and select Add Addon.
  • Add the addon zip my_addon_d9b1eedb.zip located in Torii/Product/Installer/addons/build .
  • By right clic on the addon, select Install.
    The status should be INSTALLED now.
  • Open My Addon by double clic or contextual menu or start menu. You should have:


Textfield

  • Update the file MyAddonModule.js in /js/view to add a textfield in the middle of the window. Locate the buildItems function.
  • Add the textfield in the return of function :
buildItems:function(){
    return {
   xtype: 'panel',
        layout: 'center',
        items: [{
            xtype: 'textfield',
            name: 'value',
            fieldLabel: desktop.core.utils.Symbols.getText('my_addon_d9b1eedb.textfield.input')
            + " *",
            allowBlank:false,
            listeners: {
                change: function(textfield, newValue){
                    var window = textfield.up('window');
                    window.fireEvent('textfieldChanged', textfield, newValue);
                }
            }
        }]
    };
}
  • Add the textfield label in the property file my_addon_d9b1eedb_en.properties in
    /resources/locale-bundles :
...
my_addon_d9b1eedb.textfield.input=Value
  • After a refresh of the web-browser, open My Addon and you should have:

Buttons

  • Update the file MyAddonModule.js in /js/view to add a textfield in the middle of the window. Locate the buildItems function.
  • Add buttons in the return of function as a new attribute :
...
items:me.buildItems(),
buttons:[
    {
   text: desktop.core.utils.Symbols.getText('my_addon_d9b1eedb.button.save'),
        name: 'save',
        disabled: true,
        handler: function () {
            var window = this.up('window'),
                textfield = window.down('textfield'),
                newValue = textfield.getValue();
            window.fireEvent('inputSaved', textfield, newValue);
        }
    }, {
        text: desktop.core.utils.Symbols.getText('my_addon_d9b1eedb.button.close'),
        handler: function () {
            this.up('window').close();
        }
    }
]
  • Add buttons labels in the property file my_addon_d9b1eedb_en.properties in /resources/locale-bundles :
...
my_addon_d9b1eedb.button.close=Close
my_addon_d9b1eedb.button.save=Save
  • After a refresh of the web-browser, open My Addon and you should have:

    Actually only the close button works because the event is send but never caught by controller. We will see in the next part how to make the save button operate and enabled.

Window customization

The addon window is too big, so adapt the size with the width and height attributes in the configuration of the window:

...
iconCls: me.iconCls,
width: 320,
height: 150,
name: 'my-addon-window',
...

The result should be:

If you want that the size is saved automatically, add the attribut stateId: 'my-addon-module' in the window configuration.



Textfield control

  • Add a control to manage when the textfield is set. As the value in the textfield change, the controller check if the value isn't empty. And if it's the case, the controller enable the save button. Update the file MyAddonController.js in /js/controller to add controls from the window:
control: {
    'window[name=my-addon-window]': {
   textfieldChanged: 'performTextfieldChanged'
    }
},

The name my-addon-window is to use the good window defined in the MyAddonModule.js. textfieldChanged is the name of the event fired by the textfield when the value changed. performTextfieldChanged is the function called by the controller when the event is catched.

  • Update the file MyAddonController.js in /js/controller to add the performTextfieldChanged function:
performTextfieldChanged: function(textfield, newValue){
    if(Ext.isEmpty(newValue)){
   textfield.up('window').down('button[name=save]').disable();
    } else {
        textfield.up('window').down('button[name=save]').enable();
    }
}

The textfield is now linked with the save button. If a value is set, the button is enabled, and if no value the button is disabled.


Button control

  • Update the file MyAddonController.js in /js/controller to add the save button control:
control: {
    'window[name=my-addon-window]': {
   textfieldChanged: 'performTextfieldChanged',
        inputSaved: 'performSaveInput'
    }
},

inputSaved is the name of the event fired by the save button when the user clic. performSaveInput is
the function called by the controller when the event is catched.

  • Update the file MyAddonController.js in /js/controller to add the performTextfieldChanged function:
performSaveInput: function (textfield, newValue) {
    var me = this,
   url = desktop.core.utils.Routes.getRESTUrlPrefix() + 'my_addon_d9b1eedb',
        input = {
            name: 'input',
            myValue: newValue
        };
    Ext.Ajax.request({
        url: url,
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        jsonData: input,
        success: function (response) {
            var response = Ext.decode(response.responseText),
                savedValue = response.myValue;
            textfield.up('window').down('button[name=save]').disable();
            desktop.core.utils.Logger.info(me, 'Saving value succeeded: ' + savedValue);
            Ext.Message.msg(
                desktop.core.utils.Symbols.getText('my_addon_d9b1eedb.request.title'),
                desktop.core.utils.Symbols.getText('my_addon_d9b1eedb.request.success',
                    savedValue),
                Ext.Message.statics.Names.INFO
            );
        },
        failure: function (response) {
            var response = Ext.decode(response.responseText);
            desktop.core.utils.Logger.error(me, 'Saving value failed: ' + newValue);
            Ext.Message.msg(
                desktop.core.utils.Symbols.getText('my_addon_d9b1eedb.request.title'),
                desktop.core.utils.Symbols.getText('my_addon_d9b1eedb.request.error',
                    newValue, response.message),
                Ext.Message.statics.Names.ERROR
            );
        }
    });
}

The goal is to save the value in database. We call the URL ending with /my_addon_d9b1eedb to discuss with the Rest API. The service doesn't exist now, and we will create it in the next step.

  • Add the information messages in the property file my_addon_d9b1eedb_en.properties in /resources/locale-bundles :
...
my_addon_d9b1eedb.request.title=My Addon save
my_addon_d9b1eedb.request.success=The new value <b>{0}</b> is correctly saved.
my_addon_d9b1eedb.request.error=The value <b>{0}</b> can not be saved.<br>{1}

The {0} syntax is for the second attribute of the getText() function, and the {1} is the third attribute of the getText() function.

  • Now you can try to save the value, but the Rest can not answer:


Create the following folders in /Product/Addons/Fujitsu/my_addon_d9b1eedb/Sources/:

  • backend/src/main/java/com/fujitsu/fse/torii/my_addon_d9b1eedb/dao
  • backend/src/main/java/com/fujitsu/fse/torii/my_addon_d9b1eedb/data
  • backend/src/main/java/com/fujitsu/fse/torii/my_addon_d9b1eedb/manager
  • backend/src/main/java/com/fujitsu/fse/torii/my_addon_d9b1eedb/services
    This is the skeleton of our addon backend side. You should have this:

Service

Create the service named MyAddonService.java in /my_addon_d9b1eedb/services containing:

package com.fujitsu.fse.torii.my_addon_d9b1eedb.services;
import com.fujitsu.fse.torii.my_addon_d9b1eedb.data.MyAddon;
import com.fujitsu.fse.torii.my_addon_d9b1eedb.manager.MyAddonManager;
import com.fujitsu.fse.torii.services.common.RESTService;
import javax.ws.rs.Path;
/**
 * <h1>My Addon Service</h1>
 * The service for My addon.
 *
 * @author Fujitsu Systems Europe
 * @version 1.0
 * @since 05/07/16
 */
@Path("my_addon_d9b1eedb")
public class MyAddonService extends RESTService<MyAddon> {
    //=============================================================
    // Constructors
    //=============================================================
    public MyAddonService() {
   super(MyAddonManager.class, false);
    }
}

Manager

Create the service named MyAddonManager.java in /my_addon_d9b1eedb/manager containing:

package com.fujitsu.fse.torii.my_addon_d9b1eedb.manager;
import com.fujitsu.fse.torii.data.user.User;
import com.fujitsu.fse.torii.manager.AbstractManager;
import com.fujitsu.fse.torii.my_addon_d9b1eedb.dao.MyAddonDao;
import com.fujitsu.fse.torii.my_addon_d9b1eedb.data.MyAddon;
import com.mongodb.client.MongoDatabase;
/**
 * <h1>My Addon Manager</h1>
 * The manager of My Addon
 * <p>
 * <b>Note:</b> Giving proper comments in your program makes it more
 * user friendly and it is assumed as a high quality code.
 * </p>
 *
 * @author Fujitsu Systems Europe
 * @version 1.0
 * @since 05/07/16
 */
public class MyAddonManager extends AbstractManager<MyAddonDao, MyAddon> {
    //=================================================================
    // Constructors
    //=================================================================
    /**
     * Constructs the manager
     *
     * @param requester the user who requests
     * @param database the data access object
     */
    public MyAddonManager(User requester, MongoDatabase database) {
   super(requester, new MyAddonDao(database));
    }
}

Data

Create the service named MyAddon.java in /my_addon_d9b1eedb/data containing:

package com.fujitsu.fse.torii.my_addon_d9b1eedb.data;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fujitsu.fse.torii.data.global.ToriiObject;
import org.bson.types.ObjectId;
/**
 * <h1>My addon</h1>
 * The data of My Addon
 * <p>
 * <b>Note:</b> Giving proper comments in your program makes it more
 * user friendly and it is assumed as a high quality code.
 * </p>
 *
 * @author Fujitsu Systems Europe
 * @version 1.0
 * @since 05/07/16
 */
public class MyAddon extends ToriiObject {
    //=============================================================
    // Constants
    //=============================================================
    private String myValue;
    //=============================================================
    // Constructors
    //=============================================================
    @JsonCreator
    public MyAddon(@JsonProperty("_id") ObjectId id,
                   @JsonProperty("location") String location,
                   @JsonProperty("name") String name,
                   @JsonProperty("creationDate") Long creationDate,
                   @JsonProperty("modificationDate") Long modificationDate,
                   @JsonProperty("myValue") String myValue) {
        super(id, location, name, creationDate, modificationDate);
        this.myValue = myValue;
    }
    //=============================================================
    // Public methods
    //=============================================================
    public String getMyValue() {
        return myValue;
    }
    public void setMyValue(String myValue) {
        this.myValue = myValue;
    }
}

Dao

Create the service named MyAddonDao.java in /my_addon_d9b1eedb/dao containing:

package com.fujitsu.fse.torii.my_addon_d9b1eedb.dao;
import com.fujitsu.fse.torii.dao.JacksonToriiDao;
import com.fujitsu.fse.torii.my_addon_d9b1eedb.data.MyAddon;
import com.mongodb.client.MongoDatabase;
/**
 * <h1>My Addon dao</h1>
 * The Dao of My Addon
 * <p>
 * <b>Note:</b> Giving proper comments in your program makes it more
 * user friendly and it is assumed as a high quality code.
 *
 * @author Fujitsu Systems Europe
 * @version 1.0
 * @since 05/07/16
 * </p>
 */
public class MyAddonDao extends JacksonToriiDao<MyAddon> {
    //================================================================
    // Constants
    //================================================================
/*This constant defines the collection name.*/
    private static final String COLLECTION_NAME = "my_addon_d9b1eedb_input";
    //================================================================
    // Constructors
    //================================================================
    /**
     * Constructs the Cluster Data Access Object.
     *
     * @param database the Mongo Database used for Torii
     */
    public MyAddonDao(MongoDatabase database) {
   super(database, COLLECTION_NAME, MyAddon.class);
    }
}

  • Rebuild the addon zip.
  • Update the addon from the Addon Management
  • You can now open My Addon and save the value. You should have now:
  • And to finish, go in database and look in the my_addon_d9b1eedb_input collection: