Plug-ins are one of the three basic components that can be added into the Fermat Framework. The two others are Add-ons, and GUI components. Each Plug-in has a well defined responsability within the system. Usually a Plug-in performs a task on one or more workflows.
To accomplish its mission, a Plug-in may have its own database and files to persist its state. It also has an internal structure of classes designed specifically to fullfill its goals. Some of these classes implement public interfaces which in return, exposes the Plug-in public services to other Fermat components.
Several new concepts are introduced at a Plug-in level. In this section the most important of them are explained.
### Main Classes ----------------
There are a few classes that every Plug-in has.
Each Plug-in has a Developer Class. The reason is that there might be more than one version of the Plug-in at the same time. To deal with this, instead of the Framework instantiating the Plugin Root directly, it calls the Developer Class that is somehow representing the Plug-ins developer.
By doing so, this class is able to choose which version of the Plug-in to run, and if it detects that a data migration must be done from version 1 to version 2, it coordinates that process too.
An extra function of this class is related to the licensing Developers declaring their Plug-in.
#### Plug-in Root Class
The Plug-in root is the starting point of a Plug-in. It is the point of contact between the Framework and the Plug-in. Its main purpose is to implement the interfaces that transforms itself into a service that can be started and stopped by the Framework, and other interfaces that are required in order to receive the references to other plug-ins so as to be able to consume their services.
### Class Groups -------------------
Each Plug-in gives services to other Plug-ins. They do it through public interfaces published either on Fermat API, or on the API platform where the Plug-in belongs. Some clases of its internal structure implements these public interfaces.
#### Internal Structure
Each Plug-in has an internal class model. Usually it is a hiearchy where one of it classes is the root. Some of them implements the public interface of the Plug-in defined within this Plug-in.
#### Event Handlers
An Event Handler is a type of Class that is called by the Event Manager Framework whenever an Plug-in event is suscribed to is triggered. A single Plug-in can have as many Event Handlers as needed. Somewhere within the Internal Structure the Plug-in subscribes itself to different type of events, declaring at that point which Event Handler class must be called.
#### Exceptions
Each Plugin can raise two types of exceptions:
a. Internal : These are exceptions that are thrown and handled within the same Plug-in.
b. External : These are exceptions that are thrown within a Plug-in and are expected to be catched by its caller.
NOTE | It is not allowed for an exception that is thrown in Plug-in A to go through Plug-in B unhandled and reach Plug-in C. |
---|
#### Agents
We define Agents as objects, which at runtime will create a new execution thread. We have developed this pattern in order to standardize and simplify the handling of processes that need to run from time to time and perform certain tasks.
### Other Elements -------------------
Plug-ins may have one or more Databases for storing their data. Usually one Database is enough, but in some cases different instances of the same data model are required.
#### Files
A Plug-in may have one or more files.
This section will help you understand the workflow needed to be followed in order to implement a Plug-in in Fermat.
### Getting Organized ---------------------
It is mandatory that you create an initial set of github issues before you proceed further on the workflow. This will show the rest of topics that someone is working in this functionality and avoid conflicting work early on. It will also hook the team leader into your workflow and allow him to guide and advise you when needed.
A basic hierarchy of issues is created as a first step. The issues are linked to one another just by placing a link on the first commit.
Where we refer to 'Plugin Name' what we expect is the following information:
- Platform or Super Layer name - 3 characters.
- Layer name
- Plug-in name
All of them separated by " - ".
Issues that need to be linked to its parent must have their first line starting with "Parent: " + http link to parent issue.
Team leaders are tagged in the second line in order to ask them to assign the issue to you and at the same time subscribe to any issue update. This helps team leaders to follow the issue events and provide assistance or guidance if they see something wrong. The suggested format is:
"@team-leader-user-name please assign this issue to me."
#### Plug-in Issue Structure
The mandatory initial structure is the following: (note: the word ISSUE is not part of the name)
##### ISSUE: '_Plugin Name_' - Plug-In
This is the root of your issue structure and must be labeled as SUPER ISSUE. It is closed only when all its children and grandchildren are closed.
##### ISSUE: '_Plugin Name_' - Analysis
This is the Analysis root. It is closed whenever all analysis is done. This issue must be linked to the root of the issue structure.
##### ISSUE: '_Plugin Name_' - Implementation
This is the Implementation root. It is closed whenever all implementation is done. This issue must be linked to the root of the issue structure.
1 - ISSUE: **'_Plugin Name_' - Implementation - Developer Class**
This issue is closed when this class if fully implemented.
2 - ISSUE: **'_Plugin Name_' - Implementation - Plug-in Root**
This issue is closed when this class if fully implemented.
3 - ISSUE: **'_Plugin Name_' - Implementation - Database**
This issue is closed when all database classes are fully implemented.
- ISSUE: 'Plugin Name' - Implementation - Database - Database Factory Class
This issue is closed when this class if fully implemented.
- ISSUE: 'Plugin Name' - Implementation - Database - Database Constants Class
This issue is closed when this class if fully implemented.
- ISSUE: 'Plugin Name' - Implementation - Database - Developer Database Factory Class
This issue is closed when this class if fully implemented.
- ISSUE: 'Plugin Name' - Implementation - Database - Database Factory Exceptions Class
This issue is closed when this class if fully implemented.
- ISSUE: 'Plugin Name' - Implementation - Database - DAO Class
This issue is closed when this class if fully implemented.
4 - ISSUE: **'_Plugin Name_' - Implementation - Public Interfaces**
This issue is closed when all public interface code is written. Note that the 1, 2, n must be replaced with the actual interfase names.
- ISSUE: 'Plugin Name' - Implementation - Public Interfaces - Interface 1
This issue is closed when the first public interface is written.
- ISSUE: 'Plugin Name' - Implementation - Public Interfaces - Interface 2
This issue is closed when the second public interface is written.
- ISSUE: 'Plugin Name' Implementation - Public Interfaces - Interface n
This issue is closed when the n public interfaces is written.
5 - ISSUE: **'_Plugin Name_' - Implementation - Internal Structure**
This issue is closed when all the internal code structure is written. Note that the 1, 2, n must be replaced with the actual class names.
- ISSUE: 'Plugin Name' - Implementation - Internal Structure - Class 1
This issue is closed when this class if fully implemented.
- ISSUE: 'Plugin Name' - Implementation - Internal Structure - Class 2
This issue is closed when this class if fully implemented.
- ISSUE: 'Plugin Name' - Implementation - Internal Structure - Class n
This issue is closed when this class if fully implemented.
6 - ISSUE: **'_Plugin Name_' - Implementation - Event Handling**
This issue is closed when all event handler classes are written. Note that the 1, 2, n must be replaced with the actual class names.
- ISSUE: 'Plugin Name' - Implementation - Event Handling - Event Handler 1
This issue is closed when this Event Handler class if fully implemented.
- ISSUE: 'Plugin Name' - Implementation - Event Handling - Event Handler 2
This issue is closed when this Event Handler class if fully implemented.
- ISSUE: 'Plugin Name' - Implementation - Event Handling - Event Handler n
This issue is closed when this Event Handler class if fully implemented.
##### ISSUE: '_Plugin Name_' - Testing
This is the Testing root. It is closed whenever all testing is done. This issue must be linked to the root of the issue structure.
-
ISSUE: 'Plugin Name' - Testing - Unit Testing
-
ISSUE: 'Plugin Name' - Testing - Integration Testing
##### ISSUE: '_Plugin Name_' - QA
This is the QA root. It is closed whenever QA tests are passed. This issue must be linked to the root of the issue structure.
It is expected to have here child issues in the form 'Plugin Name' QA - Bug Fix n, where n is both the number and the bug name.
##### ISSUE: '_Plugin Name_' - Production
This is the Production root. It is closed whenever the Plug-in reaches production. It can be re-opened if bug issues are found on production and closed again once they are fixed. This issue must be linked to the root of the issue structure.
It is expected to have here child issues in the form 'Plugin Name' Production - Bug Fix n, where n is both the number and the bug name.
### Analysis ------------
Yes, we have two of them and they're the following:
** The Manager Interface ** : This one is usually implemented by the Plug-in Root and its purpose is to allow the caller to have access to other functionality usually implemented on classes of the Internal Structure.
** The Deals With Interface ** : This interface is intended to be implemented by the Plug-ins consuming the service of a certain Plug-in. By implementing it, they signal the Framework to deliver them a reference of the Plug-in they need to call.
Public interfaces are defined at the API library of the Platform were the Plug-in belongs.
There is no limit regarding the interfaces a Plug-in may have. Having said that, it is highly recommended not to expose the internal structure of the Plug-in creating a Public Interface for each of the internal classes.
#### Database Model
### Implementation ------------------
Currently there are two types of implementations for this class:
a. Version 1 : It just instantiates the Plug-in Root and returns it to the Framework.
b. Version 2 : It registers each version of the Plugin root into the Framework.
To be able to use Version 2 you must verify if the Platform your Plug-in belongs to already has its own specialized core-api or instead it is started by the Framework wide Fermat-core library.
Note in the following samples that the information regarding the licensing of the Plug-in is hard-coded and always the same. This is because the licensing infraestructure is not yet in place.
Version 1
package com.bitdubai.fermat_dap_plugin.layer.identity.asset.user.developer.bitdubai;
import com.bitdubai.fermat_api.Plugin;
import com.bitdubai.fermat_api.PluginDeveloper;
import com.bitdubai.fermat_api.layer.all_definition.enums.CryptoCurrency;
import com.bitdubai.fermat_api.layer.all_definition.enums.TimeFrequency;
import com.bitdubai.fermat_api.layer.all_definition.license.PluginLicensor;
import com.bitdubai.fermat_dap_plugin.layer.identity.asset.user.developer.bitdubai.version_1.IdentityUserPluginRoot;
/**
* Created by Nerio on 07/09/15.
*/
public class DeveloperBitDubai implements PluginDeveloper, PluginLicensor {
Plugin plugin;
public DeveloperBitDubai () {
plugin = new IdentityUserPluginRoot();
}
@Override
public Plugin getPlugin() {
return plugin;
}
@Override
public int getAmountToPay() {
return 100;
}
@Override
public CryptoCurrency getCryptoCurrency() {
return CryptoCurrency.BITCOIN;
}
@Override
public String getAddress() {
return "13gpMizSNvQCbJzAPyGCUnfUGqFD8ryzcv";
}
@Override
public TimeFrequency getTimePeriod() {
return TimeFrequency.MONTHLY;
}
}
Version 2
package com.bitdubai.fermat_ccp_plugin.layer.basic_wallet.bitcoin_wallet.developer.bitdubai;
import com.bitdubai.fermat_api.layer.all_definition.common.system.abstract_classes.AbstractPluginDeveloper;
import com.bitdubai.fermat_api.layer.all_definition.common.system.exceptions.CantRegisterVersionException;
import com.bitdubai.fermat_api.layer.all_definition.common.system.exceptions.CantStartPluginDeveloperException;
import com.bitdubai.fermat_api.layer.all_definition.common.system.utils.PluginDeveloperReference;
import com.bitdubai.fermat_api.layer.all_definition.enums.CryptoCurrency;
import com.bitdubai.fermat_api.layer.all_definition.enums.Developers;
import com.bitdubai.fermat_api.layer.all_definition.enums.TimeFrequency;
import com.bitdubai.fermat_api.layer.all_definition.license.PluginLicensor;
import com.bitdubai.fermat_ccp_plugin.layer.basic_wallet.bitcoin_wallet.developer.bitdubai.version_1.BitcoinWalletBasicWalletPluginRoot;
/**
* Created by loui on 30/04/15.
* Modified by lnacosta ([email protected]) on 23/10/2015.
*/
public class DeveloperBitDubai extends AbstractPluginDeveloper implements PluginLicensor {
public DeveloperBitDubai () {
super(new PluginDeveloperReference(Developers.BITDUBAI));
}
@Override
public void start() throws CantStartPluginDeveloperException {
try {
this.registerVersion(new BitcoinWalletBasicWalletPluginRoot());
} catch (CantRegisterVersionException e) {
throw new CantStartPluginDeveloperException(e, "", "Error registering plugin versions for the developer.");
}
}
@Override
public int getAmountToPay() {
return 100;
}
@Override
public CryptoCurrency getCryptoCurrency() {
return CryptoCurrency.BITCOIN;
}
@Override
public String getAddress() {
return "13gpMizSNvQCbJzAPyGCUnfUGqFD8ryzcv";
}
@Override
public TimeFrequency getTimePeriod() {
return TimeFrequency.MONTHLY;
}
}
#### Plug-in Root
Currently there are two types of implementations for this class:
a. Version 1 : Get references of Components by implementing Deal With interfaces.
b. Version 2 : Get the references by asking the Framework to provide them.
To be able to use Version 2 you must verify if the Platform your Plug-in belongs to already has its own specialized core-api or instead it is started by the Framework wide Fermat-core library.
In this case this class implements many interfaces, Most of them to obtain references to other Components. Please note that intentionally interfaces are declared on alphabetical order and implemented in this order as well. Only the Service and Plugin interfaces are mandatory.
a. DealsWithErrors : Means that the Plug-in needs a reference to the Error Manager in order to report Unhandled Exceptions.
b. DealsWithEvents : Means that the Plug-in needs a reference to the Event Manager to either raise events or listen to other events Components.
c. DealsWithPluginDatabaseSystem : Means that the Plug-in needs a reference to the Database Manager in order to have its own databases.
d. DealsWithPluginFileSystem : Means that the Plug-in needs a reference to the File System in order to report, create, write and read files.
e. LogManagerForDevelopers : Means that the Plug-in agrees that its databases can be explored by Developers from outside the Plug-in for debbuggin purposes.
f. Plugin : Declares the current component as a Plug-in and allows it to receive its identity from the Framework. This identity allows Plug-ins for instance, to have their own set of Files and Databases . Also to ask the Framework for references to other Components (in Version 2).
g. Service : Enables the Framework to start, pause and stop the Plug-in.
h. Serializable : When present, enables the Plug-in to serialize its current state.
In this version the Plug-in Root class extends from AbstractPlugin which does the implementation of Plugin and Service by itself.
Regarding the rest of the interfaces of Version 1, in this case they don't need to be implemented as the procedure to obtain the needed references was changed in order to obtain the references, like in this example.
@NeededAddonReference(platform = Platforms.PLUG_INS_PLATFORM, layer = Layers.PLATFORM_SERVICE, addon = Addons.ERROR_MANAGER)
private ErrorManager errorManager;
@NeededAddonReference(platform = Platforms.OPERATIVE_SYSTEM_API, layer = Layers.ANDROID, addon = Addons.PLUGIN_DATABASE_SYSTEM)
private PluginDatabaseSystem pluginDatabaseSystem;
@NeededAddonReference(platform = Platforms.OPERATIVE_SYSTEM_API, layer = Layers.ANDROID, addon = Addons.PLUGIN_FILE_SYSTEM)
private PluginFileSystem pluginFileSystem;
As each Plug-in is a Service that can be started, paused and stopped by the Framework, if you need to do something when this is happening, you can override the services methods from AbstractPlugin.
SOMEBODY SHOULD CONTINUE THE STYLE, FORMAT AND QUALITY OF THE EXPLANATION THAT COMES FROM THE TOP -- Luis Molina
#### Public Interfaces
Additional notes:
- Most of the plug-ins can be consumed. For this we'll define in the api of the platform where the plug-in belongs the public interfaces of the same.
- If any method of the interface throws an exception, enums or any other, this exception must be public too, and must be created in the api too.
- We've a structure for this:
- Interfaces: Layer -> PluginName -> Interfaces
- Exceptions: Layer -> PluginName -> Exceptions
- Enums: Layer -> PluginName -> Enums
- Events: Layer -> PluginName -> Events
- Annotations: Layer -> PluginName -> Annotations
- Utils: Layer -> PluginName -> Utils
#### Database
To implement a database on a plug-in should follow a predetermined structure design, which basically consists in the creation of three classes:
- 'Name of the plug-in' Dao. This class must contain all methods that enable plug-in classes to open the database plug-in, insert, update, query, and delete records from the database.
- 'Name of the plug-in' Constants This class must include the names of each of the tables and columns of the database plug-in. The nomenclature used must follow these rules:
- You must use public static String objects.
- The name of this object must be uppercase
- You must specify what information clearly stores this column.
- You must assign a String object before mentioning it with a brief description of the content of this column, you should use lowercase and avoid special characters and use as a word separator character '_'. Here are some examples:
- Name of the database, PLUGIN_NAME_DATABASE public static final String = "plugin_database" .;
- The name of a table, PLUGIN_TABLE_NAME public static final String = "plugin_table";
- PLUGIN_TABLE_COLUMN_NAME public static final String = "any_column_name";
- 'Name of the plug-in' DatabaseFactory. This class is responsible for creating the tables in the database where the information is kept.
Additionally, if that information is required the database can be displayed through the SubApp Develop, present at runtime on the App Fermat, the implementation of the class' name plug-in'DeveloperDatabaseFactory is required. This class will be instantiated by the PluginRoot class Plug-in for achieving the aforementioned display.
To facilitate the work of creating these classes, Fermat has developed a script in Groovy, which automates the creation of these classes, following the established design, this plug-in is available at: https://github.com/bitDubai/fermat/blob/master/fermat-documentation/scripts/database/database_classes_generator/documentation_en.md and https://github.com/bitDubai/fermat/blob/master/fermat-documentation/scripts/database/database_classes_generator/script.groovy
#### Agents
The basic purpose of an agent is to run a parallel process or simultaneous to the main run of Plug-in, usually in order to achieve this we need to create classes that extend the Thread class, and rewrite the main "run (method) " which is to be run primarily when starting a new thread or process which represent this agent.
We must consider that we should not create indiscriminate agents, we always use as there is no better alternative for carrying out the task or process to be performed, as using Agents excessively may compromise the stability and performance.
Whenever you choose this solution for the activity or task required by our plug-in, we should take into account the following points:
-
The plug-in name must begin with your activity and end with the word "Agent". Example: TemplateNetworkServiceRegistrationProcessAgent.
-
The agent should be smart enough to stop automatically when contemplating your task.
-
Consider and calculate the timeout (sleeping time) between each run, the process performed by the agent, since very straight executions can reduce application performance considerably.
#### Internal Structure
The basic structure of a class that represents an agent is really simple, just take into consideration the following points:
- The implementation of an agent can be performed by inheriting from the "java.langThread" class or implementing the "java.Runnable" interface.
- At the beginning of the class you must declare a constant that indicates the downtime or waiting time thread when it is going to "sleep".
- Must be followed by any other attribute or inner member of the class.
- Continue with the implementation of the "public void run()" method, if the logic in this method is very complex and long, this logic should be developed in a separate method and the same be called within the "public void run()" method body, so make the code more readable and understandable.
Code example:
...
@Override
public void run() {
try {
while (isRunning){
complexImplementationMethod();
}
if (!isInterrupted()){
sleep(WsCommunicationVpnServerManagerAgent.SLEEP_TIME);
}
}
...
Example code of a complete class implementation:
/*
* @#WsCommunicationVpnServerManagerAgent.java - 2015
* Copyright bitDubai.com., All rights reserved.
* You may not modify, use, reproduce or distribute this software.
* BITDUBAI
*/
package com.bitdubai.fermat_p2p_plugin.layer.ws.communications.cloud.server.developer.bitdubai.version_1.structure.vpn;
import com.bitdubai.fermat_api.layer.all_definition.components.interfaces.PlatformComponentProfile;
import com.bitdubai.fermat_api.layer.all_definition.network_service.enums.NetworkServiceType;
import com.bitdubai.fermat_p2p_plugin.layer.ws.communications.cloud.server.developer.bitdubai.version_1.structure.WsCommunicationCloudServer;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
/**
* The Class <code>com.bitdubai.fermat_p2p_plugin.layer.ws.communications.cloud.server.developer.bitdubai.version_1.structure.vpn.WsCommunicationVpnServerManagerAgent</code> this
* agent manage all the WsCommunicationVpnServer created
* <p/>
* Created by Roberto Requena - ([email protected]) on 12/09/15.
*
* @version 1.0
* @since Java JDK 1.7
*/
public class WsCommunicationVpnServerManagerAgent extends Thread{
/**
* Represent the SLEEP_TIME
*/
private static long SLEEP_TIME = 60000;
/**
* Holds the vpnServersActivesCache
*/
private List<WsCommunicationVPNServer> vpnServersActivesCache;
/**
* Represent the lastPortAssigned
*/
private Integer lastPortAssigned;
/**
* Represent the hostIp
*/
private String hostIp;
/**
* Represent the isRunning
*/
private boolean isRunning;
/**
* Constructor whit parameter
*
* @param serverIp
* @param cloudServerIp
*/
public WsCommunicationVpnServerManagerAgent(String serverIp, Integer cloudServerIp){
this.vpnServersActivesCache = new ArrayList<>();
this.lastPortAssigned = cloudServerIp;
this.hostIp = serverIp;
this.isRunning = Boolean.FALSE;
}
/**
* Create a new WsCommunicationVPNServer
*
* @param participants
*/
public WsCommunicationVPNServer createNewWsCommunicationVPNServer(List<PlatformComponentProfile> participants, WsCommunicationCloudServer wsCommunicationCloudServer, NetworkServiceType networkServiceTypeApplicant) {
InetSocketAddress inetSocketAddress = new InetSocketAddress(hostIp, (lastPortAssigned+=1));
WsCommunicationVPNServer vpnServer = new WsCommunicationVPNServer(inetSocketAddress, participants, wsCommunicationCloudServer, networkServiceTypeApplicant);
vpnServersActivesCache.add(vpnServer);
vpnServer.start();
return vpnServer;
}
/**
* (non-javadoc)
* @see Thread#run()
*/
@Override
public void run() {
try {
//While is running
while (isRunning){
//If empty
if (vpnServersActivesCache.isEmpty()){
//Auto stop
isRunning = Boolean.FALSE;
this.interrupt();
}
for (WsCommunicationVPNServer wsCommunicationVPNServer : vpnServersActivesCache) {
try {
/*
* Send the ping message to this participant
*/
wsCommunicationVPNServer.sendPingMessage();
}catch (Exception ex){
//Close all connection and stop the vpn server
wsCommunicationVPNServer.closeAllConnections();
wsCommunicationVPNServer.stop();
vpnServersActivesCache.remove(wsCommunicationVPNServer);
}
}
if (!isInterrupted()){
sleep(WsCommunicationVpnServerManagerAgent.SLEEP_TIME);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* (non-javadoc)
* @see Thread#start()
*/
@Override
public synchronized void start() {
isRunning = Boolean.TRUE;
super.start();
}
/**
* Get the is running
* @return boolean
*/
public boolean isRunning() {
return isRunning;
}
}
IMPORTANT:
-
Agents should have the ability to stop automatically when their task is completed.
-
It is recommended as good practice, that before calling the method "public static native void sleep(long millis) throws InterruptedException;" you evaluate if the thread has not been interrupted by another process before putting it into downtime.
Code example:
...
if (!isInterrupted()){
sleep(WsCommunicationVpnServerManagerAgent.SLEEP_TIME);
}
...
#### Event Handlers
-
Location of a Plug-in
-
The build.gradle File
-
Gradle Plug-ins
-
Folder Structure
-
Main Packages
-
Test Packages
-
The Database Script