1.Legal Notice
2.Flexicore Documentation
General Class Diagram Notes
class diagrams in this documentation are not showing the full inheritance of the entities and include only the entities that reside in the same software component .
most entities in the class diagram have Baseclass
as direct or in-direct ancestor.
Acknowledgement
this documentation structure was inspired by Spring Boot Documentation.
3.Getting Started
If you are getting started with FlexiCore, start by reading this section. It answers the basic “what?”, “how?” and “why?” questions. It includes an introduction to FlexiCore, along with installation instructions. We then walk you through building your first FlexiCore application, discussing some core principles as we go.
3.1.Introducing FlexiCore
FlexiCore makes it easy to create pluggable, production-grade Spring-based Applications that you can Run.
Everything is a Plugin
with the concept of service plugins and entities plugins it supports all of the Spring features in plugins ( dependency injection, rest APIs, event bus, etc), this allows developers to create logically separated units (with dependency among them) that are self-contained, this allows for better source control, faster development cycles and fewer bugs in production.
The new FlexiCore-boot is built on Spring modules and allows a much smaller memory-footprint and fewer dependencies. FlexiCore-Boot can add FlexiCore plugins support to an existing or a new Spring-Boot application. Created plugins can be injected into each other.
FlexiCore Boot allows developers to create the host application loading the plugins with ease , it allows limiting the supported features for plugins loaded by the host application allowing to minimize the application foot print.
When the application requires additional features such as REST endpoints, FlexiCore access control to data, etc. These additional features are added to your application as Spring modules.
3.2.System Requirements
FlexiCore requires java 8 and is compatible up to java 14 (included).
FlexiCore requires a postgresql database version 9+, a mongodb database version 3+ and maven 3.3+ to build.
3.3.Installing FlexiCore
FlexiCore can be used with “classic” Java development tools or installed as a command line tool. Either way, you need Java SDK v1.8 or higher. Before you begin, you should check your current Java installation by using the following command:
$ java -version
3.4.Developing Your First FlexiCore Application
This section describes how to develop a simple “Hello World!” web application that highlights some of FlexiCore’s key features. We use Maven to build this project since most IDEs support it.
Before we begin, open a terminal and run the following commands to ensure that you have valid versions of Java and Maven installed:
$ java -version java version "1.8.0_102" Java(TM) SE Runtime Environment (build 1.8.0_102-b14) Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14, mixed mode)
$ mvn -v Apache Maven 3.5.4 (1edded0938998edf8bf061f1ceb3cfdeccf443fe; 2018-06-17T14:33:14-04:00) Maven home: /usr/local/Cellar/maven/3.3.9/libexec Java version: 1.8.0_102, vendor: Oracle Corporation
This sample needs to be created in its own directory. Subsequent instructions assume that you have created a suitable directory and that it is your current directory.
3.4.1.Creating the POM
We need to start by creating a Maven pom.xml file. The pom.xml is the recipe that is used to build your project. Open your favorite text editor and add the following:
Maven Dependency for plugins:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.wizzdi.examples</groupId> <artifactId>example-service</artifactId> <version>1.0.0</version> <properties> <flexicore-api.version>4.0.12</flexicore-api.version> <maven.compiler.source>1.8</maven.compiler.source> <version.compiler.plugin>3.3</version.compiler.plugin> <version.eclipselink>2.7.7</version.eclipselink> <maven.compiler.target>1.8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <pf4j-spring.version>0.6.0</pf4j-spring.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.3.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-spring-boot-starter</artifactId> <version>4.0.0.Final</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.wizzdi</groupId> <artifactId>flexicore-api</artifactId> <version>${flexicore-api.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>io.swagger.core.v3</groupId> <artifactId>swagger-jaxrs2</artifactId> <version>2.0.8</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.pf4j</groupId> <artifactId>pf4j-spring</artifactId> <version>${pf4j-spring.version}</version> <scope>provided</scope> </dependency> </dependencies> <build> <resources> <resource> <filtering>true</filtering> <directory>src/main/resources</directory> </resource> </resources> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> </plugin> <plugin> <artifactId>maven-shade-plugin</artifactId> <version>3.2.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <minimizeJar>false</minimizeJar> <createDependencyReducedPom>true</createDependencyReducedPom> <dependencyReducedPomLocation>${java.io.tmpdir}/dependency-reduced-pom.xml </dependencyReducedPomLocation> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <Plugin-Id>${artifactId}</Plugin-Id> <Plugin-Version>${version}</Plugin-Version> </manifestEntries> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
note that all of these dependencies are provided scoped , thats becuase they exist in your FlexiCore-exec-*.jar
At this point, you could import the project into an IDE (most modern Java IDEs include built-in support for Maven). For simplicity, we continue to use a plain text editor for this example.
3.4.2.Writing The Code
To finish our application, we need to create a single Java file. By default, Maven compiles sources from src/main/java
, so you need to create that directory structure and then add a file named src/main/java/ExampleRESTService.java
to contain the following code:
package com.flexicore.examples.rest; import com.flexicore.annotations.OperationsInside; import com.flexicore.annotations.ProtectedREST; import com.flexicore.annotations.plugins.PluginInfo; import com.flexicore.interfaces.RestServicePlugin; import com.flexicore.security.SecurityContext; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.pf4j.Extension; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import javax.ws.rs.*; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; /** * Created by Asaf on 04/06/2017. */ @PluginInfo(version = 1) @OperationsInside @ProtectedREST @Path("plugins/Example") @Tag(name = "Example") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @Component @Extension @Primary public class ExampleRESTService implements RestServicePlugin { @GET @Operation(summary = "hello", description = "hello") @Path("hello") public String hello( @HeaderParam("authenticationKey") String authenticationKey, @Context SecurityContext securityContext) { return "world"; } }
the class must implement Plugin
interface to be identified by FlexiCore , this class implements RestServicePlugin
as it contains Rest Services , usually classes are implementing RestServicePlugin
(for REST), ServicePlugin
(for general business logic class) or extending AbstractRepositoryPlugin
(for repository)
the annotations in class can be divided into groups:
- JAX-RS annotations - annotations for expressing the REST api , more info in JAX-RS docs ( these are the
@GET,@Produces,@Consumes,@Path,@HeaderParam,@Context
annotations) - PF4J annotations -
@Extension
annotation , this will add this class to PF4J's extensions.idx file. - OpenAPI(Swagger) annotations -
@Tag
annotation - Spring annotations -
@Primary,@Component
more info in spring docs - FlexiCore annotations :
@PluginInfo(version = 1)
- tells FlexiCore this bean should be loaded as version 1 ( FlexiCore allows running plugins of different versions at the same time, see Same service in multiple versions )-
@OperationsInside
tells FlexiCore this class contains operations for Access Control -
@ProtectedREST
tells FlexiCore that rest methods in this class are protected and requires Authentication and authorization -
@Operation
tells FlexiCore the information for a specific method , will create an operation with the given name and description
3.4.3.Running the Example
at this point your plugin should work . create a jar using mvn package
command and place the output jar into FlexiCore's plugin directory.
if FlexiCore was already running stop it and start it again using :
java -Dloader.main=com.flexicore.init.FlexiCoreApplication -Dloader.path=file:/home/flexicore/entities/ -jar /opt/flexicore/flexicore.jar --spring.config.additional-location=file:///home/flexicore/config/
If you open a web browser to http://localhost:8080/FlexiCore/rest/Example/hello , you should see the following output:
world
To gracefully exit the application, press ctrl-c
.
3.5.What to Read Next
Hopefully, this section provided some of the FlexiCore basics and got you on your way to writing your own applications. If you are a task-oriented type of developer, you might want to jump over to wizzdi.com and check out some of the getting started guides that solve specific “How do I do that with FlexiCore?” problems. We also have FlexiCore-specific “How-to” reference documentation.
Otherwise, the next logical step is to read Using FlexiCore. If you are really impatient, you could also jump ahead and read about FlexiCore features.
4.Using Flexicore
5.Flexicore Features
FlexiCore Provides a unique set of features in many domains and fields, those are described in the following sections.
5.1.FlexiCore Boot Host Application
FlexiCore Boot allows creating a host application for plugins that enables only certain features from plugins , this allows a smaller footprint and faster loading times as redundant modules are not packaged.
5.1.1.FlexiCore Boot Modules
FlexiCore Boot Modules are dependencies added to FlexiCore's Host Application. those modules usually enable feature support in plugins , for example JAX-RS , Graphql, actuator and more.
5.1.1.1.FlexiCore Boot (basic module)
The FlexiCore Boot Module enables plugin loading.
the maven dependency is :
<dependency> <groupId>com.wizzdi</groupId> <artifactId>flexicore-boot</artifactId> <version>LATEST</version> </dependency>
to enable FlexiCore Boot Module add the @EnableFlexiCorePlugins
annotation above you Spring Boot Application Class.
more info can be found in the module's repository.
5.1.1.2.FlexiCore Boot Starter Web
The FlexiCore Boot Starter Web Module enables Spring Rest Controllers in plugins.
the maven dependency is :
<dependency> <groupId>com.wizzdi</groupId> <artifactId>flexicore-boot-starter-web</artifactId> <version>LATEST</version> </dependency>
To enable FlexiCore Boot Starter Web Module add the @EnableFlexiCoreRESTPlugins
annotation above you Spring Boot Application Class.
more info can be found in the module's repository.
5.1.1.3.FlexiCore Boot Starter WebSockets
The FlexiCore Boot Starter Websocket Module enables Javax websockets in plugins.
the maven dependency is :
<dependency> <groupId>com.wizzdi</groupId> <artifactId>flexicore-boot-starter-websocket</artifactId> <version>LATEST</version> </dependency>
To enable FlexiCore Boot Starter Websocket Module add the @EnableFlexiCoreWebSocketPlugins
annotation above you Spring Boot Application Class.
more info can be found in the module's repository.
5.1.1.4.FlexiCore Boot Starter Actuator
The FlexiCore Boot Starter Actuator Module enables Spring's Actuator in plugins.
the maven dependency is :
<dependency> <groupId>com.wizzdi</groupId> <artifactId>flexicore-boot-starter-actuator</artifactId> <version>LATEST</version> </dependency>
To enable FlexiCore Boot Starter Actuator Module add the @EnableFlexiCoreHealthPlugins
annotation above you Spring Boot Application Class.
more info can be found in the module's repository.
5.1.1.5.FlexiCore Boot Starter RESTEasy
The FlexiCore Boot Starter RESTEasy Module enables JAX-RS in plugins.
the maven dependency is :
<dependency> <groupId>com.wizzdi</groupId> <artifactId>resteasy-flexicore-boot-starter</artifactId> <version>LATEST</version> </dependency>
To enable FlexiCore Boot Starter RESTEasy Module add the @EnableFlexiCoreJAXRSPlugins
annotation above you Spring Boot Application Class.
more info can be found in the module's repository.
5.1.1.6.FlexiCore Boot Starter GraphQL
The FlexiCore Boot Starter GraphQL Module enables GraphQL in plugins.
the maven dependency is :
<dependency> <groupId>com.wizzdi</groupId> <artifactId>graphql-flexicore-boot-starter</artifactId> <version>LATEST</version> </dependency>
To enable FlexiCore Boot Starter GraphQL Module add the @EnableFlexiCoreGraphqlPlugins
annotation above you Spring Boot Application Class.
more info can be found in the module's repository.
5.1.1.7.FlexiCore Boot Starter Flyway
The FlexiCore Boot Starter Flyway Module enables Flyway database migrations in plugins.
the maven dependency is :
<dependency> <groupId>com.wizzdi</groupId> <artifactId>flexicore-boot-starter-flyway</artifactId> <version>LATEST</version> </dependency>
To enable FlexiCore Boot Starter Flyway Module add the @EnableFlexiCoreFlyWayPlugins
annotation above you Spring Boot Application Class.
more info can be found in the module's repository.
5.1.1.8.FlexiCore Boot Starter Data JPA
The FlexiCore Boot Starter Data JPA Module provides opinionated eclipselink support in entity plugins.
the maven dependency is :
<dependency> <groupId>com.wizzdi</groupId> <artifactId>flexicore-boot-starter-data-jpa</artifactId> <version>LATEST</version> </dependency>
To enable FlexiCore Boot Starter Data JPA Module add the @EnableFlexiCoreJPAPlugins
annotation above you Spring Boot Application Class.
more info can be found in the module's repository.
5.1.1.9.FlexiCore Boot Starter Data REST
The FlexiCore Boot Starter Data REST Module provides support for Spring's Data REST in plugins.
the maven dependency is :
<dependency> <groupId>com.wizzdi</groupId> <artifactId>flexicore-boot-starter-data-rest</artifactId> <version>LATEST</version> </dependency>
To enable FlexiCore Boot Starter Data REST Module add the @EnableFlexiCoreDataRESTPlugins
annotation above you Spring Boot Application Class.
more info can be found in the module's repository.
5.2. Spring Empowered Plugins
FlexiCore supports most of spring capabilities in its plugins , writing service or model type plugin is described in the sections below.
5.2.1.Introduction
the common solutions when designing a software system are:
- Create a monolithic application, use discipline, and patterns to keep your system manageable.
- Use micro-services.
- Micro-services are complex to manage and come with inherent drawbacks related to how they co-work, how the database is designed, maintained, and shared.
- plugin-based architecture, like OSGi, etc.
FlexiCore based Architecture
Use FlexiCore to build an entire system from plugins using Spring the way you know and like.
Enjoy a 100% modular system where different, loosely coupled components are used to build an entire system.
While there are solutions for plugins support for a spring boot application using PF4J, none provides the ability to access plugin's services from another plugin.
There were efforts to allow OSGi based system to provide a full web, database solution using Apache Karaf. However, this solution provides little benefits if at all for backend development.
Why inter-injected-plugins is essential for truly modular development:
- When services need to be used by other services, a non-dependency between plugins solution forces the use of a single plugin for all associated services. This tends to create a plugin that can be as complex and difficult to maintain as a monolithic application.
- When plugins cannot be inter-injected, reuse of your or third party plugins is impossible.
How inter-injected-plugins in flexicore provides a solution:
A fundamental advantage available in Flexicore is the ability to inject plugins into other plugins and extend database entities defined in a plugin in another plugin dependent on it directly or indirectly.
This unique ability of Flexicore makes development in Flexicore truly modular while allowing development to reuse and modify existing plugins' services and entities.
This is carried out without breaking the nature of Spring development.
Plugins support dependency injection among them and entity inheritance among entity plugins, there are no limitations to what can be implemented by plugins and FlexiCore development paradigm is:
Everything is a plugin
Flexicore in Mirco-services development.
FlexiCore can be used to build more modular micro-services while reusing plugins across multiple micro-services. This is especially useful if multiple micro-services use the same database.
Flexicore in Mirco-services development
Once the required plugins are created, all the model-plugins (also called entities) should be stored in a pre-configured location (the default is /home/flexicore/entities for Linux).
Deploying Flexicore plugins.
The service-plugins should be stored after build in a pre-configured location (the default is /home/flexicore/plugins for Linux).
Alternatively, plugins update service can be used through the proper APIs, see: Software Updater.
5.2.2.Model Plugins
The model is the collection of your entities and external entities (from Flexicore and other plugins) used to describe the persistent classes and their relations.
The model is described in code and it is almost fully identical with standard JPA entities.
When creating the model for a Flexicore application the following should be noted:
- Check Flexicore documentation and available plugins for existing entities you can extend. Inheritance is a very important paradigm in Flexicore.
- Decide early on what are the relationships between entities. A many-to-many relationship requires a connecting entity, there are many examples in Flexicore examples and Flexicore itself.
- Use annotations to create indexes in the relational database so the performance of queries will be largely unrelated to the database size
5.2.2.1.Creating Entities
Entities are created using the same annotations and principles used in standard JPA entities' creation.
See for example:
package com.flexicore.example.person; import com.flexicore.model.Baseclass; import com.flexicore.security.SecurityContext; import javax.persistence.Entity; @Entity public class Person extends Baseclass { public Person(String name, SecurityContext securityContext) { super(name, securityContext); } public Person() { } private String firstName; private String lastName; public String getFirstName() { return firstName; } public <T extends Person> T setFirstName(String firstName) { this.firstName = firstName; return (T) this; } public String getLastName() { return lastName; } public <T extends Person> T setLastName(String lastName) { this.lastName = lastName; return (T) this; } }
Note the constructor and inheritance, in order to enjoy Flexicore Access Control features, all entities extend Baseclass or any of its children. It is pretty much like Object in Java being the ancestor of all classes.
for a full, simple example, checkout the example's Github repository
few notes regarding this example:
- The persistence.xml in resources folder should have a reference to the entities of this plugin, see below.
- Services based on these entities reference the entities through a dependency entry in their pom file. follow Creating the POM for more info.
.... <class>com.flexicore.model.Baseclass</class> <class>com.flexicore.example.person.Person</class> <class>com.flexicore.example.library.model.Author</class> <class>com.flexicore.example.library.model.Book</class> <class>com.flexicore.example.library.model.Subscription</class> <exclude-unlisted-classes>true</exclude-unlisted-classes> ....
Extending Baseclass
Extending Baseclass is not mandatory and entities can be created as in any Spring boot application. However, some of the more powerful features of Flexicore will not be available. The most notable one is Flexicore high-performance multi-tenancy Access Control.
Some of the available plugins, synchronization, for instance, will not work with entities not extending Baseclass directly or indirectly.
5.2.2.2.Dynamic Schema
Entities in the system can have additional fields that are not defined in the schema.
When a dynamic container includes unknown fields, these are saved in the PostgreSQL database as BSON (Binary JSON).
Queries can use these fields and test for equality.
Enabling Dynamic Schema
enabling dynamic schema is easily done by having the object received from the client for the creation of the desired entity extend BaseclassCreate
and implement the supportingDynamic method below :
@Override public boolean supportingDynamic() { return true; }
BaseclassNewService.updateBaseclassNoMerge
should be called prior of merging the object
additional data will be saved in Baseclass.jsonNode
property
Enabling Dynamic Filtering
enabling dynamic schema is easily done by having the filtering object used to fetch the entity extends FilteringInformationHolder
and implementing the method below :
@Override public boolean supportingDynamic() { return true; }
Saving Dynamic Properties
when creating a Dynamic Schema enabled entity any unknown JSON properties or nested properties will be saved:
{ "example": { "description": "this is a dynamic object", "test":1 } }
in the above example the field example (object)
will be saved along with other properties for the object.
Filtering by Dynamic Properties
when fetching a dynamic enabled entity, filtering by dynamic properties is done by adding the desired value for desired fields, for example:
{ "example": { "test":1 } }
this will return all entities of the requested type which has the value of test
property inside example
set to 1.
5.2.2.3.Choosing Persistence Strategy
Flexicore uses two different types of databases.
PostgresSQL is the main database and is used for the Flexicore model and the Entities described in plugins.
MongoDB (no-SQL) database is used for immutable data only, such as auditing, events searchable logs, health information, etc.
Both databases are supported by the multi-node synchronization plugin. However, Flexicore access control to instances is only provided for the relational database.
Access control to instances on the no-SQL database is normally provided using two steps, find the limiting data in the relational database, for example, user id(s), and filter the no-SQL data using these fields.
Access control to API can be used regardless of the target database used.
Access to NO-SQL is provided through MongoDB Java SDK. No ORM framework is used in conjunction with MongoDB.
5.2.3.Service Plugins
Services plugins manage everything outside of entities. Services represent all logic in a plugin.
Services can co-reside on the same server at different versions using the same API.
Services can be injected into other services, Injected services are the depend-on services where inject-into services are the dependent services.
5.2.3.1.Dependency among Service Plugins
FlexiCore can be used to develop full systems based only on plugins. This is possible as services in a plugin A can be injected into plugin B, Plugin B depends on Plugin A.
FlexiCore takes care of injecting Spring beans. The correct version is injected if specified, otherwise the default version is provided.
for example:
package com.flexicore.examples.service; import com.flexicore.annotations.plugins.PluginInfo; import com.flexicore.data.jsoncontainers.PaginationResponse; import com.flexicore.example.library.model.Author; import com.flexicore.examples.data.AuthorRepository; import com.flexicore.examples.request.AuthorCreate; import com.flexicore.examples.request.AuthorFilter; import com.flexicore.examples.request.AuthorUpdate; import com.flexicore.interfaces.ServicePlugin; import com.flexicore.model.Baseclass; import com.flexicore.security.SecurityContext; import org.pf4j.Extension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import java.util.List; import java.util.logging.Logger; @PluginInfo(version = 1) @Component @Extension @Primary public class AuthorService implements ServicePlugin { @PluginInfo(version = 1) @Autowired private AuthorRepository repository; @PluginInfo(version = 1) @Autowired private PersonService personService;
In the code above has two injected Beans, an AuthorRepository instance that is defined in the same jar (in this case plugin jar) the above code is extracted from.
On the other hand, the injected PersonService is from another plugin and is defined in a different jar, FlexiCore takes care of instantiating the required bean and injecting the instance into the AuthorService plugin.
In order to make the person-service plugin available to library-service during compile-time the following steps should be followed:
- The dependency definition in the library-service project is defined:
<dependency> <groupId>com.wizzdi.examples</groupId> <artifactId>person-service</artifactId> <version>${person-service.version}</version> <scope>provided</scope> </dependency>
Note that the scope for this dependency is provided, that is, the actual compiled code is provided by FlexiCore at run time and it is not packaged into the library-service jar.
- the manifest of library-service should be updated so the dependency is known at runtime , this is easily done by using pfj4's maven plugin defined in the pom.xml file (note the Plugin-Dependencies tag):
<plugin> <artifactId>maven-shade-plugin</artifactId> <version>3.2.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <minimizeJar>false</minimizeJar> <createDependencyReducedPom>true</createDependencyReducedPom> <dependencyReducedPomLocation>${java.io.tmpdir}/dependency-reduced-pom.xml </dependencyReducedPomLocation> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <Plugin-Id>${artifactId}</Plugin-Id> <Plugin-Version>${version}</Plugin-Version> <!--suppress UnresolvedMavenProperty --> <Plugin-Dependencies>person-service@>=${person-service.version} </Plugin-Dependencies> </manifestEntries> </transformer> </transformers> </configuration> </execution> </executions> </plugin>
In order to make the person-service plugin available to library-service during runtime, the following steps should be followed:
- library-service should be placed in the plugins folder
- person-service should be placed in the plugins folder
- any other plugin that library-service or person-service depend on should be placed in the plugins folder
- any entity-based plugin that library-service or person-service depend on should be placed in the entities folder
In Linux, this is by default: /home/flexicore/plugins and is defined in Spring application.properties file.
5.2.3.2.REST Service in Service Plugins
A service plugin may include API endpoints. The definition is similar to normal REST endpoint definitions in Spring with some additional fields required for the system persistence based access-control.
For example, from Flexicore examples
package com.flexicore.examples.rest; import com.flexicore.annotations.OperationsInside; import com.flexicore.annotations.ProtectedREST; import com.flexicore.annotations.plugins.PluginInfo; import com.flexicore.data.jsoncontainers.PaginationResponse; import com.flexicore.example.library.model.Author; import com.flexicore.examples.request.AuthorCreate; import com.flexicore.examples.request.AuthorFilter; import com.flexicore.examples.request.AuthorUpdate; import com.flexicore.examples.service.AuthorService; import com.flexicore.interfaces.RestServicePlugin; import com.flexicore.security.SecurityContext; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import javax.ws.rs.*; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import org.pf4j.Extension; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import org.springframework.beans.factory.annotation.Autowired; @PluginInfo(version = 1) @OperationsInside @ProtectedREST @Path("plugins/Author") @Tag(name = "Author") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @Component @Extension @Primary public class AuthorRESTService implements RestServicePlugin { @PluginInfo(version = 1) @Autowired private AuthorService authorService; @POST @Path("createAuthor") @Operation(summary = "createAuthor", description = "Creates Author") public Author createAuthor( @HeaderParam("authenticationKey") String authenticationKey, AuthorCreate authorCreate, @Context SecurityContext securityContext) { authorService.validate(authorCreate, securityContext); return authorService.createAuthor(authorCreate, securityContext); }
The RestServicePlugin
Every class containing REST service endpoints should implement RestServicePlugin.
API endpoints defined in such class are added to the list of REST endpoints provided by Flexicore itself and to these defined in other plugins and in other classes implementing this interface.
Flexicore Specific annotations on the class
- @PluginInfo(version = 1)
- set the version of this class, multiple versions of the same API can co-exist in the server if versions are different. The client code (JS, TS etc.) can specify the preferred version, this version will be selected by the system for this request.
- @OperationsInside
- Instructs Flexicore to search for new Operations in this class when initializing, operations are stored in the database and used for access control. For example, this class includes a definition of the createAuthor operation. Operation annotation used here is from Swagger and is used by Flexicore. Swagger can be used for the documentation of the API and for out of the box display of available endpoints.
- @ProtectedREST
- Set all endpoints in this class to be protected, users must be signed-in in order to access any endpoint defined here. A SecurityContext object is passed and the access control system is associated with both the Operation and the data it creates/updates or views. This annotation must be used on licensed features too.
- @Extension
- This is not Flexicore annotation. This is PF4J annotation and defines the class as a plugin. The Spring implementation of Flexicore uses PF4J.
SecurtiyContext and authentication key
Each protected endpoint must have as first header parameter the authentication key obtained when the user has signed-in.
@HeaderParam("authenticationKey") String authenticationKey,
The SecurtiyContext must be present too, this is injected by the system and is used in inferring user rights to access this endpoint. The SecurityContext instance is part of the parameters passed downstream so access to data can use it when executing the desired operation.
@Context SecurityContext securityContext
5.2.3.3.Business Logic in Service Plugins
Business flow services are classes implementing the ServicePlugin interface.
Methods in such classes are usually called from RestServicePlugin and are provided with a SecurityContext instance so access control can properly function.
In most cases, a container instance is passed for a create or update operations and a filter instance is passed for aggregation and listing operations.
Business logic should be put here (recommended), data access operations dealing with entities required for a business logic operation should call a data repository defined in this or another plugin.
See below a service class from Flexicore examples
package com.flexicore.examples.service; import com.flexicore.annotations.plugins.PluginInfo; import com.flexicore.data.jsoncontainers.PaginationResponse; import com.flexicore.example.library.model.Author; import com.flexicore.examples.data.AuthorRepository; import com.flexicore.examples.request.AuthorCreate; import com.flexicore.examples.request.AuthorFilter; import com.flexicore.examples.request.AuthorUpdate; import com.flexicore.interfaces.ServicePlugin; import com.flexicore.model.Baseclass; import com.flexicore.security.SecurityContext; import org.pf4j.Extension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import java.util.List; import java.util.logging.Logger; @PluginInfo(version = 1) @Component @Extension @Primary public class AuthorService implements ServicePlugin { @PluginInfo(version = 1) @Autowired private AuthorRepository repository; @PluginInfo(version = 1) @Autowired private PersonService personService; public Author createAuthor(AuthorCreate authorCreate, SecurityContext securityContext) { Author author = createAuthorNoMerge(authorCreate, securityContext); repository.merge(author); return author; } public Author createAuthorNoMerge(AuthorCreate authorCreate, SecurityContext securityContext) { Author author = new Author(authorCreate.getFirstName(),securityContext); updateAuthorNoMerge(author, authorCreate); return author; } public boolean updateAuthorNoMerge(Author author, AuthorCreate authorCreate) { boolean update =personService.updatePersonNoMerge(author,authorCreate); return update; } public Author updateAuthor(AuthorUpdate authorUpdate, SecurityContext securityContext) { Author author = authorUpdate.getAuthor(); if (updateAuthorNoMerge(author, authorUpdate)) { repository.merge(author); } return author; } public <T extends Baseclass> T getByIdOrNull(String id, Class<T> c, List<String> batchString, SecurityContext securityContext) { return repository.getByIdOrNull(id, c, batchString, securityContext); } public PaginationResponse<Author> getAllAuthors(AuthorFilter authorFilter, SecurityContext securityContext) { List<Author> list = listAllAuthors(authorFilter, securityContext); long count = repository.countAllAuthors(authorFilter, securityContext); return new PaginationResponse<>(list, authorFilter, count); } public List<Author> listAllAuthors(AuthorFilter authorFilter, SecurityContext securityContext) { return repository.listAllAuthors(authorFilter, securityContext); } public void validate(AuthorFilter authorFilter, SecurityContext securityContext) { } public void validate(AuthorCreate authorCreate, SecurityContext securityContext) { } }
Notes
- Line 21 defines the service version, if a different plugin defines the same class with a different version number, the system will provide both, in such case the API plugin of each version is supposed to call the correct service, that is, from the client specifying a different version of the API to the server the execution chain is different. It is assumed that changes to the model (entities) if any, support all active service versions. The persistence (database) is always at the latest version and is expected to be backward compatible.
- Line 29 injects the AuthorRepository. The version is specified, it may be defined in a different plugin and will be properly injected depending on the version.
- Line 33 injects a PersonService, the version is also defined and is provided by a plugin matching the version.
5.2.3.4.Data Repositories
The data repository class is responsible for interacting directly with the data. This is a recommended and not a mandatory structure.
The actual instantiation of classes in the example is carried out at the service level, however, the service calls the data repository to update or query the database.
Such a design pattern allows easy replacement of the actual data handling using SQL statements (unrecommended) or even a NO-SQL database.
In Flexicore data repositories need to extend the AbstractRepositoryPlugin class.
The code below is an example of such a class taken from the examples repository in Github.
The AbstractRepositoryPlugin provides a set of data access methods as depicted in the system Javadocs.
package com.flexicore.examples.data; import com.flexicore.annotations.plugins.PluginInfo; import com.flexicore.example.library.model.Author; import com.flexicore.examples.request.AuthorFilter; import com.flexicore.interfaces.AbstractRepositoryPlugin; import com.flexicore.model.QueryInformationHolder; import com.flexicore.security.SecurityContext; import org.pf4j.Extension; import org.springframework.stereotype.Component; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import java.util.ArrayList; import java.util.List; @PluginInfo(version = 1) @Extension @Component public class AuthorRepository extends AbstractRepositoryPlugin { public List<Author> listAllAuthors(AuthorFilter filtering, SecurityContext securityContext) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Author> q = cb.createQuery(Author.class); Root<Author> r = q.from(Author.class); List<Predicate> preds = new ArrayList<>(); addAuthorPredicate(filtering, cb, r, preds); QueryInformationHolder<Author> queryInformationHolder = new QueryInformationHolder<>( filtering, Author.class, securityContext); return getAllFiltered(queryInformationHolder, preds, cb, q, r); } private void addAuthorPredicate(AuthorFilter filtering, CriteriaBuilder cb, Root<Author> r, List<Predicate> preds) { PersonRepository.addPersonPredicate(filtering, cb, r, preds); } public Long countAllAuthors(AuthorFilter filtering, SecurityContext securityContext) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Long> q = cb.createQuery(Long.class); Root<Author> r = q.from(Author.class); List<Predicate> preds = new ArrayList<>(); addAuthorPredicate(filtering, cb, r, preds); QueryInformationHolder<Author> queryInformationHolder = new QueryInformationHolder<>( filtering, Author.class, securityContext); return countAllFiltered(queryInformationHolder, preds, cb, q, r); } }
Notes:
- The listAllAuthors method is an example of getting a filtered list of something that extends Baseclass in this case, the caller has passed an instance of AuthorFilter that extends a FilteringInformationHolder.
- The filter is used to create a list of predicates passed to the CriteriaQuery.
- The returned result is of type PaginationResponse<Author>, it is strongly recommended to avoid getting all instances of something in one call, Flexicore provides the required filtering and response types supporting pages.
5.2.3.5.Dynamic Service Lookup
When plugin A is extended by an unknown number of plugins, for example when a defrayment service may use an unknown number of providers, such as credit card charging services, the flow will be:
the defrayment service plugin A provides such services regardless of the provider, for example in a checkout service.
Plugin A can be injected with any number of Interface X (Java interface). Interface X itself is defined in plugin A or in any service Plugin A depends on.
Any plugin designed to be used by Plugin A should :
- Depend on plugin A
- implement Interface X
The framework injects all beans implementing Interface X into Plugin A. Plugin A has no dependency on any Interface X implementors and it can provide if required, a list of such implementors.
5.2.3.6.Multi-Versioned Service Plugins
5.3.Access Control
Flexicore access control supports multi-tenancy, uses the database for all settings and definition, that is, it doesn't rely on hardcoded policies.
Flexicore access control system offers very fine granularity access control to data along with powerful default behavior policies for maximum efficiency of permissions persistence.
When entities are created in plugins support for Flexicore access control is implicitly provided. This is true if entities are created following the inheritance rules.
5.3.1.Multi-Tenancy
Introduction
Flexicore system is designed to provide a very flexible and powerful multitenancy access control system.
Multitenancy is required when a server is built to provide services to businesses or to a group of users managed as such.
The access control system restricts access to API calls, internal calls, and the data the system manages. The system is designed to avoid any hard-coded roles and policies, instead, access control is fully defined in the database. The only code association is through the definition of the Operation annotation.
Instances of the Operation class are stored in the database when the system starts, if not previously stored
Access to the API can be managed in much higher granularity.
When new Operation instances defined in plugins they are stored in the database. The accumulated list of all Operations from plugins and Flexicore itself is available through the system API and the system generic management console.
In order to reduce the number of database rows created to store permissions and policies, the system uses a default set of policies. Default behaviors can easily be defined through the APIs or through the provided management console. When not defined, the system imposes its own default behaviors.
A set of API calls is provided for managing access control related entities in the system.
Instances of entities defined in plugins get full access control as long as they are defined to extend the Baselclass class from flexicore.api or any entity that has Baseclass as a superclass, directly or indirectly.
For more information about multi-tenancy see multitenancy
5.3.2.Tenants, Role and Users
Flexicore access control system makes use of the following Entities:
Tenant
A tenant Defines an isolated dataset that can be regarded as a separate database, when a Tenant is created, the administrator of the tenant is created too.
The Administrator of a tenant can create new tenants and create new administrators for any tenant he or she controls.
The permissions system can be used to block the new tenant creation by a tenant administrator.
Important points to know about tenants:
- Any instance of any entity has a Tenant field, this is true for all entities directly or indirect;y extending
Baseclass
. - Users have a default tenant.
- The tenant field of any instance is the default tenant of the user creating the instance. This includes instances created indirectly by system objects such as DynamicExecution instances.
- Users can be associated with more than a single tenant and view data across many tenants based on the access control policies defined.
- Roles (see below) belong to a single tenant.
- When the system is created, a tenant called defaulttenant is created along with a super administrator.
- Users of a tenant can be granted access to any piece of data in the system regardless of their default tenant.
- System objects required by more than one tenant can be grouped in a PermissionGroup see below and permitted at the tenant level.
Role
The role entity groups multiple users so permissions can be granted to all members of the role.
Users can be members of multiple roles. The more permissive policy applies. For example, if a user is a member of the viewer role and of the administrator role and viewers cannot edit an instance while administrators can, the user will be permitted to edit the said instance.
Roles are defined per tenant.
User
Usually describes a person, users are identified through two unique identifiers, their email and the id the system created (GUID) when the user is created. Users have default tenant but can access multiple tenants if so permitted.
5.3.3.Operations and Type-Based Access Control
5.3.4.API Access control
API calls access control:
REST API calls are invoked from a client are usually annotated with the Operation annotation. When the server starts, it adds these operations to the database, if not already added, so the REST method is now defined in the database and can be used for access rights. Access control on API calls permits or blocks an Operation without checking what are the Objects to be affected or fetched. Checking the instances access right is always performed only when the operation itself is allowed.
When simple CRUD operations are needed, developers can use built-in operations instead of defining new ones, these are Read, Write, Delete, and Update. Using custom Operations is required when a finer granularity of access control is needed.
Invokers, methods, and Dynamic Executions.
Flexicore includes a powerful generic system for internal server methods invocations called Invokers. Invokers are used with Flexicore rules engine, scheduling, and with Flexicore generic user interface support. When a Dynamic Execution is created (a combination of Invoker, method, parameters/filters ) the creator access rights are used when the Dynamic Execution needs to run. The set of access rules apply in the same way these are applied to REST endpoints operations.
Flow
When a REST call is invoked, the system checks the following:
- Checks if this Operation is allowed for the current user. If yes, proceed
- If not check if this operation is denied for the current user, if yes, block the operation.
- Check if Operation permitted for any of the Roles the current user belongs to, if yes, proceed.
- Check if Operation is denied for any role the user belongs to, if yes, block the operation.
- At this point, the system checks the default behavior as follows:
- Check if the current Tenant (the Tenant the user is logged into) is allowed by default to perform the operation, if yes, proceed to perform the operation.
That is, whether all tenants members can perform the operation if an explicit allow or deny wasn’t defined directly on the user or any role the user belongs to.
- Check if the current Tenant is denied by default to perform the operation if yes, block the operation.
- If none of the above tests has ended with a proceed or block, check if the operation was allowed or denied by the default behavior for the operation, this is defined in the code.
The diagram below depicts the sequence of operations when deciding if an API call is permitted.
The same flow is applied when Dynamic Executions are executed.
Note: as performance is a key target for us, the above is executed in one database operation

5.3.5.Data Access control
Instance access control is implemented through a SecurityLink between a User, a Role, or a Tenant with the instance/class in question and the operation to be performed.
Such a link connects to instances (in practice, rows in the database) and has two additional fields as follows: A complex value is an instance of Operation type and the simple value(String) is either ‘Deny’ or ‘Allow’
A SecurityLink defines the right to access or the lack of it (denial) in the following possible scenarios:
- Between a User and an instance of an object (any object).
- Between a Role and an instance of an object, thus saving the need to define permission for users associated with a Role (users can be members of multiple roles). Access to the object will be permitted if any of the roles the user is associated with is permitted to access the instance.
- Between a Tenant and instance of the Clazz type, every instance in the system is associated with a type, types are stored in the database in a table called Clazz. When a tenant is associated with permission with an instance of a Clazz, it practically defines the default access behavior for users of this tenant for this type(Clazz) for this Operation. For example, The administrator of the Tenant is likely to have special permission (or the Administrators Role) as described above to override the default tenant behavior and to allow Administrators to manage users. Note that this is different for granting access to the Operation as described above, here a certain User (can be part of a Role called Administrators or any other name) was granted access to specific instances of the User type, so for example, a user who is the manager of the organization can be made blocked to other users even if they belong to a Role who was granted access to the User Clazz.
Note that a SecurityLink is always associated with an Operation, so for defining the default behavior for hundred instances of operations (the system may have many such operations in addition to the normal 4 -read, write, delete, update) hundred permissions between the tenant and each type of instance (Clazz) must be defined, otherwise, users will see only objects they have created. Therefore, the system may define default behavior for any operation, and for the matter also for any clazz.
If such a security link is defined in a tenant with the ‘Allow’ attribute, it implies that unless explicitly defined otherwise all instances in the tenant are accessible to all users.
The details:
- Any instance is accessible for any Operation in context if the logged-in user is the creator of the instance.
- Otherwise, an instance is accessible by Operation in context to all users that have explicit permission on that instance with an ‘allow’ value. If such permission exists with a ‘deny’, access to that object will be blocked.
- In case no explicit permission exists for the current user, permission between any Role the user belongs to is looked for. If such permission exists, it behaves in a similar way to the behavior of User permission. If the permission has an ‘allow’ value, the operation will be allowed on this instance, if a ‘deny’ value is defined, the instance will not be accessed and further tests are terminated (for this instance).
- In case no explicit permission exists for any of the Roles of the current User, permission between any such Role the type (Clazz) of the instance is looked for. If such permission exists, access is either blocked or granted based on the value 'deny, if a ‘deny’ value is defined, the instance will not be accessed and further tests are terminated (for this instance).
- The last tests for this instance are related to the default behavior for this Tenant and the Operation in question. This is tested for the type or class that the Operation needs to access (for example list all Users in the system is associated with the type User). If a permission link exists between the Tenant and the Clazz in question (that is, the Type in context), the value in that link dictates the access right to all instances of this type for the Operation in context. An ‘Allow’ value will grant access while a ‘Deny’ value will block it. Overriding this default Tenant behavior is done through the Role and User Permissions described above.
5.3.5.1.Permission Groups
Permission groups were created to allow multiple instances to be managed, permission-wise, as one.
In a way, permission groups are symmetrical in concept to Roles. Roles are there to facilitate the same behavior to a group of Users. Permission Groups allow the same behavior to a group of instances.
Properties of Permission Groups.
- A permission group can contain instances of any type.
- Instances can be members of multiple permission groups.
- When a SecurityEntity (Tenant, Role, User) has permission to some operation on a permission group instance, it is similar to having specific permission of such operation on any instance included in the permission group.
- Instances in a permission group must extend Baseclass either directly or indirectly.
See relevant APIs and the system provided user interface for managing permissions.
5.3.7.Queries and Filters
when you need to fetch access control filtered information from the database you will need to create a repository which extends AbstractRepositoryPlugin
and call getAllFiltered
, countAllFiltered
or prepareQuery
for more advanced cases .these receive a FilteringInformationHolder
or extenders and an optional SecurityContext
if not provided no access control filters will be done . additional filtering may be added using JPA's Criteria API.
SecurityContext
is usually obtained from REST api's but can also be obtained directly from SecurityService
FilteringInformationHolder
Comes out of the box with additional filters which are enforced when getAllFiltered
, countAllFiltered
or prepareQuery
are called.
starting a criteria api query from Author
CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Author> q = cb.createQuery(Author.class); Root<Author> r = q.from(Author.class);
adding additional filters( simple predicate)
if(filtering.getNames()!=null&&!filtering.getNames().isEmpty()){ preds.add(r.get(Author_.name).in(filtering.getNames())); }
a more advanced filtering including join:
if(filtering.getBooks()!=null&&!filtering.getBooks().isEmpty()){ Set<String> ids=filtering.getBooks().stream().map(f->f.getId()).collect(Collectors.toSet()); Join<Author, Book> join=r.join(Author_.books); preds.add(join.get(Book_.id).in(ids)); }
calling getAllFiltered , constructing the required QueryInformationHolder
and providing SecurityContext
QueryInformationHolder<Author> queryInformationHolder = new QueryInformationHolder<>( filtering, Author.class, securityContext); return getAllFiltered(queryInformationHolder, preds, cb, q, r);
here is the full example repository:
@PluginInfo(version = 1) @Extension @Component public class AuthorRepository extends AbstractRepositoryPlugin { public List<Author> listAllAuthors(AuthorFilter filtering, SecurityContext securityContext) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Author> q = cb.createQuery(Author.class); Root<Author> r = q.from(Author.class); List<Predicate> preds = new ArrayList<>(); addAuthorPredicate(filtering, cb, r, preds); QueryInformationHolder<Author> queryInformationHolder = new QueryInformationHolder<>( filtering, Author.class, securityContext); return getAllFiltered(queryInformationHolder, preds, cb, q, r); } private void addAuthorPredicate(AuthorFilter filtering, CriteriaBuilder cb, Root<Author> r, List<Predicate> preds) { //adding person filters , since AuthorFilter inherites from PersonFilter IPersonRepository.addPersonPredicate(filtering, cb, r, preds); //filter authors by given names if(filtering.getNames()!=null&&!filtering.getNames().isEmpty()){ preds.add(r.get(Author_.name).in(filtering.getNames())); } //filter author by specific books if(filtering.getBooks()!=null&&!filtering.getBooks().isEmpty()){ Set<String> ids=filtering.getBooks().stream().map(f->f.getId()).collect(Collectors.toSet()); Join<Author, Book> join=r.join(Author_.books); preds.add(join.get(Book_.id).in(ids)); } } public Long countAllAuthors(AuthorFilter filtering, SecurityContext securityContext) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Long> q = cb.createQuery(Long.class); Root<Author> r = q.from(Author.class); List<Predicate> preds = new ArrayList<>(); addAuthorPredicate(filtering, cb, r, preds); QueryInformationHolder<Author> queryInformationHolder = new QueryInformationHolder<>( filtering, Author.class, securityContext); return countAllFiltered(queryInformationHolder, preds, cb, q, r); }
5.3.8.Access Control Management System
5.4.Dynamic invokers
Dynamic Invokers are services that can be executed through a known rest API endpoint, FlexiCore allows to enumerate dynamic invokers methods and parameters allowing the caller to implement a generic code for executing them it is in concept similar to GraphQL
Dynamic invokers are useful when we would like to support multiple services with the same logic without requiring a client-side change.
1.CoffeeMakerA
2.CoffeeMakerB
and assuming we would like each to implement a "makeCoffee" service :
Using REST API
we can create two rest APIs for each one /rest/plugins/CoffeeMakerA/makeCoffee
and /rest/plugins/CoffeeMakerB/makeCoffee
the issue here is that our client has to be aware of CoffeeMakerA and CoffeeMakerB, so once we add a new coffee marker client code will have to change
Using Java Interfaces
another solution will be to have all coffeeMakers implement an interface:
public interface CoffeeMaker { void makeCoffee(MakeCoffeeRequest makeCoffeeRequest); }
and create a single rest API in the plugin defining that interface /rest/plugins/CoffeeMaker/makeCoffee
this rest api will go over all coffeeMakers get the required coffee maker which is one of the parameters for MakeCoffeeRequest .
the issue here is that the client cant use a different parameter for each coffee marker if it is required or has to be aware of CoffeeMaker specific parameters which will again require client-side code
Using Invokers
each plugin will implement an invoker with a method called makeCoffee , a common object that may or may not be used to describe the request, client-side will call /rest/DynamicInvokers/getAllInvokers
to get the available invokers in the system taking only those implementing makeCoffee method allowing the user to select the relevant one and setting the parameters for it. if a new CoffeeMarkerC will be added no change is required on client-side
Two options for invokers creation
Historically, invokers were implemented in code, this is still provided, however, the latest versions of FlexiCore (version 5.0 or newer) support automatic invoker creation from all REST endpoints, thus when a plugin adds an API endpoint, it becomes available as a method in an Invoker represented by the Class containing the method.
An example of an Invoker created from a REST API endpoint.
Method Categories
Implementing an Invoker manually
invoker is a Service Plugin that implements the Invoker
interface and contains methods annotated with @InvokerMethodInfo
here is our CoffeeMakerAInvoker
class
package com.flexicore.examples.service; import com.flexicore.annotations.plugins.PluginInfo; import com.flexicore.interfaces.dynamic.Invoker; import com.flexicore.interfaces.dynamic.InvokerMethodInfo; import com.flexicore.security.SecurityContext; import org.pf4j.Extension; import org.springframework.stereotype.Component; @PluginInfo(version = 1) @Extension @Component public class CoffeeMakerAInvoker implements Invoker { @InvokerMethodInfo(displayName = "makeCoffee", description = "Makes coffee using Coffee Maker's A Service", relatedClasses = {Coffee.class}) public Coffee makeCoffee(MakeCoffeeRequest makeCoffeeRequest,SecurityContext securityContext) { // make coffee logic } @Override public Class<?> getHandlingClass() { return Coffee.class; } }
ListingInvoker
extends the invoker interface requiring the implementor to implement a listAll method and give a filter class type
@InvokerMethodInfo
contains fields for better describing the method to the client such as displayName,description and relatedClasses
to allow the client to show all relevant parameters for the makeCoffee method we need to annotate MakeCoffeeRequest
fields with matching annotations:
@FieldInfo
- basic field, allows setting name,description,mandatory(true or false),defaultValue,validation(string)
@IdRefFieldInfo
- a string field or a list of strings that references a specific type (this allows the client side to use another invoker to list possible values for that type) allows setting displayName,Description,mandatory(true or false), list(true or false if this field is of type string or list of strings),refType(the given ids are the types of this class)
@ListFieldInfo
-same as FieldInfo but for a list, this is different from @IdRefFieldInfo
since the values in the list are not ids of a different entity
here is our MakeCoffeeRequest
class:
package com.flexicore.examples.service; import com.flexicore.interfaces.dynamic.FieldInfo; import com.flexicore.interfaces.dynamic.IdRefFieldInfo; import com.flexicore.interfaces.dynamic.ListFieldInfo; public class MakeCoffeeRequest { @FieldInfo(displayName = "Customer Name",description = "Customer requesting coffee",mandatory = true,defaultValue = "John Doe") private String customerName; @IdRefFieldInfo(displayName = "Coffee Machine",description = "The Coffee Machine that will be used to make the coffee",refType = CoffeeMachine.class,list = false) private String coffeeMachineId; @ListFieldInfo(displayName = "Extra Ingredients",description = "Extra Ingredients to add to coffee",listType =Ingredient.class ) private List<Ingredient> extraIngredients; public String getCustomerName() { return customerName; } public <T extends MakeCoffeeRequest> T setCustomerName(String customerName) { this.customerName = customerName; return (T) this; } public String getCoffeeMachineId() { return coffeeMachineId; } public <T extends MakeCoffeeRequest> T setCoffeeMachineId(String coffeeMachineId) { this.coffeeMachineId = coffeeMachineId; return (T) this; } }
MakeCoffeeRequest
should be registered when the context is started, note that request objects that are used in a listAll
method are registered with their respective filters
@PluginInfo(version = 1) @Extension @Component public class Config implements ServicePlugin { @EventListener public void init(PluginsLoadedEvent e) { BaseclassService.registerFilterClass(CoffeeMachineFilter.class, CoffeeMahcine.class); CrossLoaderResolver.registerClass(MakeCoffeeRequest.class); } }
Common Client-Side Sequence
- The client-side will call
/rest/DynamicInvokers/getAllInvokers
the response will contain a list of invoker classes, their methods, and the parameters for each method - The client-side will allow the user to select/automatically select using some logic the relevant invoker(s) and a method
- The client-side will present the parameters and allow the user to input values when a field is referencing another entity (the parameter has a nonnull
idRefType
) a selection will be shown from the result of the listAll invoker method for that given type (in our example when handling the fieldcoffeeMachineId
clients side will attempt to find an invoker which has ahandlingType
ofcom.flexicore.examples.service.CoffeeMachine
with alistAll
method) - The client-side will call
/rest/dynamicInvokers/executeInvoker
providing:-
invokerNames
- canonical names for the relevant invoker classes obtained from phase 1 (in our example itscom.flexicore.examples.service.CoffeeMakerAInvoker
) -
invokerMethodName
- (the method on the invoker(s) to call , in our example itsmakeCoffee
) -
executionParametersHolder
- the parameters for the method including atype
field with the canonical name of the request object (in our example itscom.flexicore.examples.service.MakeCoffeeRequest
)
-
Dynamic Execution
dynamic execution is an entity holding a specific execution parameters for invoker(s) method , this enable the re-use of that action dynamic execution is widely used in Rules Engine,Grid preset and Scheduling
Creating DynamicExecution
/rest/dynamicInvokers/createDynamicExecution
is the endpoint for creating a dynamic execution which expects the same as the executeInvoker api discussed in the previous paragraph and an additional name and description parameters to allow identifying the dynamic execution object.
/rest/dynamicInvokers/updateDynamicExecution
is the endpoint for updating a dynamic execution
the executionParametersHolder field holding the parameters must be a JPA entity following the JPA guidelines which is defined in the Domain model
5.5.File management support
FlexiCore provides a File Management system out of the box. this component revolves around FileResource
entity which most notably has the following fields:
-
md5
- the file md5 -
offset
- the file resource current offset -
done
- true/false if the file upload was completed -
keepUntil
- if set FlexiCore will delete this file oncekeepUntil
date has passed -
originalFilename
- the original file name the file had before being uploaded( flexicore creates a random name for the actual file in the file system)
here are some important APIs:
Upload
-
FlexiCore/rest/resources/{md5}
will return a file resource object with the given md5 or empty response if non exists FlexiCore/rest/resources/upload
supports chunk upload and receives binary (octet-stream) body that will be written from the current offset of the file resource and headers:-
md5
client-side calculated md5 of the entire file -
name
filename ( this will be saved asoriginalName
onFileResource
-
chunkMd5
the current chunk md5 (optional - will not be enforced if not sent), this is available for optional retry on chunk if the communication link is unreliable or slow. -
lastChunk
- Boolean specifying if this is the last chunk or not, if it is total file md5 will be validated and the file will be closed for changes
-
Sequence for uploading files
- calculate the local file md5
- call
FlexiCore/rest/resources/{md5}
to get the current file offset, if the offset equals the file size, there is no need to upload it. - read a chunk from the offset from the local file, the chunk size is arbitrary and can be selected to best-fit link bandwidth and reliability - note that most http servers set a limit to the request size , the chunk size should not exceed this limit.
- calculate chunk md5 ( optional , if chunk md5 is used)
- upload chunk calling
FlexiCore/rest/resources/upload
API, update offset counter from returnedFileResource
- repeat 3-5 until completion ( setting
lastChunk
to true for the last chunk)
the upload sequence supports pausing/resuming uploads and makes sure that a completed file always has the correct md5
Download
FlexiCore/rest/download/{authenticationkey}/{id}
- sends binary content of the file with id using authenticationKey provided , this API is secured by permissions
FlexiCore/rest/downloadUnsecure/{authenticationkey}/{id}
- sends binary content of the file with id, this API is NOT secured by permissions, the whole access to the API can be disabled using the Access Control Management System
FileResourceService
it is possible to create FileResource
objects from FlexiCore plugins, simply @Autowired
FileResourceService
to use its CRUD methods ( createDontPersist,createNoMerge,create,listAllFileResources,updateFileResourceNoMerge
and more).
5.6.Authentication and Authorization
FlexiCore has an authentication and authorization support out of the box. Successful authentication will result in an authentication key.
every valid authentication key received by a secure API (REST or WebSocket) results in a SecurityContext
object which can be used by plugins services to obtain relevant/allowed entities for the user by providing the SecurityContext
to the getAllFiltered
method in the Access Control section. SecurityContext
is also used to deny/allow access for a specific user/role/tenant for a specific operation .
let us assume we have a getAllPerson
service which returns all the persons the user allowed to see. A failed operation access control will result in a 403 forbidden HTTP response. A successful data access control may result in an empty list if the user does not have access to any person entity, this as explained in the Access Control section is supported out of the box by FlexiCore.
Obtaining the Authentication Token
obtaining authentication key by calling endpoint /FlexiCore/rest/authenticationNew/login
providing email or phone number and a mandatory password, the API will return a token with its expiration date
Using the Authentication Token
most FlexiCore APIs expect the authentication token to be provided as a header named authenticationKey
Writing a Secure REST API
- REST API class must be annotated with
@ProtectedREST
- the method must be annotated with FlexiCore's
@Operation
annotation or FlexiCore's@Read,@Write,@Update,@Delete
- method first parameter must be a string representing the authentication token
- methods last parameter must be of type
SecurityContext
annotated by@Context
an example can be found in Writing The Code section
SecurityService Programmatic interface
occasionally we would like to get a security context for a specific user in a plugin running code, FlexiCore Allows us doing that by calling SecurityService.getUserSecurityContextByEmail(userEmail)
or SecurityService.getUserSecurityContext(user)
if the full user object is available
UserService Programmatic interface
occasionally we would like to get an autheticationToken for a specific user in a plugin running code, FlexiCore Allows us doing that by calling UserService.registerUserIntoSystem(user)
or UserService.registerUserIntoSystem(user,expirationDate)
this allows the plugin author to create a custom authentication mechanism.
5.7.Auditing
Listing Auditing Events
The endpoint for listing auditing events is: /FlexiCore/rest/audit/getAllAuditingEvents
it allows filtering auditing events by operation (list of operation ids), Users (list of user ids), and a date range. See the AuditingFiltering class.
Enabling Auditing for Operation
calling:/FlexiCore/rest/operations/setOperationAuditable/{id}
providing the id for the operation to be audited
listing operations is possible using the endpoint
/FlexiCore/rest/operations/listAllOperations
5.8.Available plugins
FlexiCore provides a set of plugins out of the box which expands the capabilities of the system.
5.8.1.IoT
The IoT Service supports sending/receiving messages to/from a remote FlexiCore server.
this section refers to the following software components:
- flexicore-iot-service
- flexicore-iot-model
Concepts
FlexiCore address the issue of IoT system where you have multiple remote IoT devices required to connect to a "Cloud" FlexiCore Server where those devices might not be accessible directly (since they are behind a router) by proposing the following solution:
- IoT device is a Linux or Windows based device.
- IoT device follows the System Requirements.
- IoT device runs FlexiCore.
- IoT device runs any configuration of the services in the "Cloud" and all of the entities in the "Cloud". Some of the IoT device services can be omitted from the cloud and vice-versa.
- IoT device is configured to connect to the "Cloud" FlexiCore Server - solving the NAT issue.
note that sometimes an IOT device does not have the adequate computing power to support the above requirements (like embedded devices and sensors) or the IoT device needs to run on battery for a long period , in such a cases it is recommended to use a gateway device which will addresses the above requirements and implements a connector that receives the data from the unsupported device on local communication using any porotocl.
As Flexicore hardware requirements are quite modest and it runs on small and inexpensive devices, there is a huge advantage in using the same software architecture on the cloud, gateways and edge devices.
One of the more common scenarios where an edge device cannot be based on Felxicore is when the edge device needs to be battery driven and battery life needs to be long. In such case, battery powered devices connected to a local Flexicore gatwwaye can be used. A plugin implemented this connection is required.
FlexiCoreServer
is an entity that models a remote or local FlexiCore Server, this object will be used to determine the designation of IoT messages.
Configuring local FlexiCore Server
it is expected that the property flexicore.iot.id
will be configured in FlexiCore's configuration file, it is also expected that a JSON describing the remote FlexiCore server will be set in flexicore.iot.remoteConfigLocation
, here is an example of such file:
{ "name":"cloud-test", "description":"cloud-test", "enabled":true, "externalId":"cloud-test", "basePathApi":"http://localhost:8080/FlexiCore/rest", "webSocketPath":"ws://localhost:8080/FlexiCore/iotWSGZIP", "username":"admin@flexicore.com", "password":"admin" }
if flexicore.iot.id
matches the externalId
field in the JSON it means that a FlexiCoreServer
the instance should be created upon startup that describes this (local) server.
Managing FlexiCore Servers
FlexiCore allows creating updating and listing FlexiCore servers using the following endpoints:
/FlexiCore/rest/flexicoreServer/createFlexiCoreServer
- creates aFlexiCoreServer
/FlexiCore/rest/flexicoreServer/registerFlexiCoreServer
- when an unknownFlexiCoreServer
attempts connecting to ourFlexiCoreServer
an instance ofUnRegisteredFlexiCoreServer
will be created and the connection will be closed. this api allows registering one of thoseUnRegisteredFlexiCoreServer
as a properFlexiCoreServer
./FlexiCore/rest/flexicoreServer/updateFlexiCoreServer
- updates aFlexiCoreServer
/FlexiCore/rest/flexicoreServer/getFlexiCoreServers
- listsFlexiCoreServer
s
or using the following service methods
FlexiCoreServerService.createFlexiCoreServer
- creates aFlexiCoreServer
FlexiCoreServerService.registerFlexiCoreServer
- when an unknownFlexiCoreServer
attempts connecting to ourFlexiCoreServer
an instance ofUnRegisteredFlexiCoreServer
will be created and the connection will be closed. this api allows registering one of thoseUnRegisteredFlexiCoreServer
as a properFlexiCoreServer
.FlexiCoreServerService.updateFlexiCoreServer
- updates aFlexiCoreServer
FlexiCoreServerService.getFlexiCoreServers
- listsFlexiCoreServer
s
FlexiCore Servers Health
remote FlexiCore servers send health information every interval (interval defined on the FlexiCoreServer
instance or if zero taken from flexicore.iot.defaultHealthCheckInterval
which defaults to 300000 ms)
the latest health check is held on the FlexiCoreServer
instance FlexiCoreServer.isHealthy()
and FlexiCoreServer.lastHealthData()
.
history of health checks is also available by calling the endpoint /FlexiCore/rest/healthReport/getAllHealthReport
or by calling HealthReportService.getAllHealthReport
Sending IoT Messages
sending FlexiCoreIOTRequest
or extenders is possible using IOTService.sendRequest(communicationId,flexiCoreExecutionRequest,c,callback)
where :
communicationId
- remoteFlexiCoreServer
externalIdflexiCoreExecutionRequest
- request to sendc
- type for the expected callback-
callback
- callback that will be called once a response for the sent request is received
Receiving IoT Messages
services may register to receive FlexiCoreIOTRequest
of certain types by using IOTService.registerMessageListener(c,priority,callback)
where:
c
- type for the expected callbackpriority
- callbacks for the same type will be called by priority order (low is first high is last)callback
- callback that will be called once the request of the given type is received
Existing IoT Messages
HealthUpdateRequest
will be sent with health dataHealthUpdateResponse
will be sent once health data was receivedFlexiCoreServerConnected
will be sent when a FlexiCore server is trying to connect to remote FlexiCore serverFlexicoreServerConnectedResponse
will be sent with remote server response detonating if it was connected successfully or notFlexiCoreServerDisconnected
if possible a FlexiCoreServer will send this message when it disconnects
Custom IoT Message Types
developers may extend FlexiCoreIOTRequest
and FlexiCoreIOTResponse
to send custom IOT requests and responses
5.8.2.Synchronization
Synchronization Service allows syncing entities between FlexiCore servers.
this section refers to the following software components:
- flexicore-sync-service
- flexicore-sync-model
Synchronization relies on IOT.
Instances of entities are synchronized among Flexicore servers if the instance was marked to be synchronized, this carried out by the service creating the instance.
Marking an Baseclass
Entity to be Synced with remote FlexiCore Server
developers need to create a FlexiCoreServerToBaseclass
using the endpoint:
-
FlexiCore/rest/plugins/flexiCoreServerToBaseclass/createFlexiCoreServerToBaseclass
- providing theFlexiCoreServer
's id and the entity to be synced id
or using the service
-
FlexiCoreServerToBaseclassService.createFlexiCoreServerToBaseclass
- providing theFlexiCoreServer
and the entity to be synced
once this link is created the given entity will be synced to remote FlexiCore Server if it is ever updated
Marking an BaseclassNoSQL
Entity to be Synced with remote FlexiCore Server
developers need to create a NoSQLSyncLink
using the endpoint:
-
FlexiCore/rest/plugins/noSQLSyncLink/createNoSQLSyncLink
- providing theFlexiCoreServer
's id and the entity to be synced id
or using the service
-
NoSQLSyncLinkService.createNoSQLSyncLink
- providing theFlexiCoreServer
and the entity to be synced
once this link is created the given entity will be synced once and the link will be later deleted
Registering callbacks when an Entity is received
- developers may register a callback that will be called once entity of a specific type is received by the syncing service
SyncService.registerReceiveType(c,callback)
where:c
- the class of the entity that the callback will be called forcallback
- the callback the will be called once the entity is received
- developers might require to compare the received entity state with the existing state of the entity:
- a converter should first be registered to hold the state of the entity before it was received by calling
SyncService.registerConverter(c,syncRegister)
where:c
- the class of the entity that the converter will receivesyncRegister
- function receiving the entity and returning an object describing its state, it can be any object
- a callback receiving the converted object and the received entity should be registered by calling
registerCompareCallback(c,postMergeCallback)
where:c
- the class of the entity that the converter will receivepostMergeCallback
- function receiving the received entity and the contained entity describing the state before the sync returning null
- a converter should first be registered to hold the state of the entity before it was received by calling
Versioning Synced Entities
sometimes we might want to update the model in the local server and not in the remote server (or vice-versa), in this case introducing a new non-basic (annotated with JPA's @ManyToOne or @OneToOne) field will break the integrity of the deserialization on the receiving end. FlexiCore allows the developer to solve this problem by using the @SyncOption
version field, the developer should annotate the class with @SyncOption(version=X)
where X is the current version of the entity and should annotate the getter of the non-basic field with @SyncOption(version=Y)
where Y is the version the complex field was added in, in this case, FlexiCore will sync the entity setting null value in that field for the remote FlexiCoreServer , once the remote FlexiCoreServer is update to version >=Y this entity will be synced again if it was no updated locally in the remote FlexiCoreServer. here is an example of a versioned entity:
package com.flexicore.example.library.model; import com.flexicore.annotations.sync.SyncOption; import com.flexicore.model.Baseclass; import com.flexicore.security.SecurityContext; import javax.persistence.Entity; import javax.persistence.ManyToOne; @Entity @SyncOption(version = 4) public class Book extends Baseclass { public Book() { } public Book(String name, SecurityContext securityContext) { super(name, securityContext); } @ManyToOne(targetEntity = Author.class) private Author author; @SyncOption(version = 3) @ManyToOne(targetEntity = Author.class) public Author getAuthor() { return author; } public <T extends Book> T setAuthor(Author author) { this.author = author; return (T) this; } }
this means that the current version of Book
entity is 4 and author
field was added in version 3.
5.8.3.Software Updater
The software update plugin supports the updating and the installation of new components on a remote flexicore server.
In general, the required instances needed on a remote node are transferred through the Flexicore synchronization plugin.
this section refers to the following software component:
- software-update-service
- software-update-model
- command-executor
the software update mechanism relies on Multi-node synchronization and IOT
Software Update Service And Model
Software update service manages SoftwareUpdate,SoftwareUpdateBundle,SoftwareUpdateInstallation
:
SoftwareUpdate
- an update of the following types:- Service - updates a flexicore service plugin - the received file is a service jar
- Model - updates a flexicore entities plugin - the received file is an entities jar
- Core - updates flexicore - the received file is a FlexiCore jar
- Generic - generic updates - the received file is a zip that contains install.sh script, the script runs from the root of the extracted zip folder.
SoftwareUpdateBundle
- a package of multipleSoftwareUpdate
SoftwareUpdateInstallation
- an installation request of aSoftwareUpdateBundle
on aFlexiCoreServer
( for more onFlexiCoreServer
see Multi-node synchronization and IOT) , it contains the current status of installation and any output received while running the installation
installing software on remote FlexiCore server sequence:
- if a software installation bundle exists skip to phase 5
- create all the relevant SoftwareUpdates objects using the endpoint
/FlexiCore/rest/plugins/SoftwareUpdate/createSoftwareUpdate
- create a
SoftwareUpdateBundle
by calling/FlexiCore/rest/plugins/SoftwareUpdateBundle/createSoftwareUpdateBundle
- for each update from section 2 connect the bundle with that update by calling
/FlexiCore/rest/plugins/UpdateToBundle/createUpdateToBundle
- create
SoftwareUpdateInstallation
by calling/FlexiCore/rest/plugins/SoftwareUpdateInstallation/createSoftwareUpdateInstallation
Command Executor
For FlexiCore to run the updates in a reliable manner it must have the command executor run as a service, it is in charge of restarting services, updating flexicore, and generally running all the commands received from the local flexicore server.
Command executor listens by default on the loopback interface , it is configurable but it is recommended not to set it to listen to public interfaces for security reasons.
Installing Command Executor on Ubuntu
assuming the command-executor-*.jar is at /home/flexicore/command-executor-*.jar
here is the required systemd service:
[Unit] Description=Command Executor After=syslog.target network.target Before=httpd.service [Service] User=root LimitNOFILE=102642 PIDFile=/var/run/commandExecutor/commandExecutor.pid ExecStart=/usr/bin/java -jar /home/flexicore/command-executor-1.0.0.jar --logging.file=/var/log/commandExecutor/command-executor.log StandardOutput=null [Install] WantedBy=multi-user.target
Note: The Command executor runs as root user , this is required for privileged actions such as machine/service restart and manipulating files in privileged locations.
5.8.4.Rules Engine
5.8.5.Organization management
The Organization Service supports managing organization objects: Branch,Customer,Employee,Industry,Organization,SalesPerson,SalesRegion,Site,Supplier
this section refers to the following software components:
- organization-service
- organization-model
Organization Service depends on Address Management component.
Managing Organization Entities
managing organization objects is possible using the following endpoints:
/FlexiCore/rest/plugins/Branch/getAllBranches
- getting all branches/FlexiCore/rest/plugins/Branch/createBranch
- create branch/FlexiCore/rest/plugins/Branch/updateBranch
- update branch/FlexiCore/rest/plugins/Customer/getAllCustomers
- getting all customers/FlexiCore/rest/plugins/Customer/createCutomer
- create customer/FlexiCore/rest/plugins/Customer/updateCustomer
- update customer/FlexiCore/rest/plugins/Employee/listAllEmployees
- getting all employees/FlexiCore/rest/plugins/Employee/createEmployee
- create employee/FlexiCore/rest/plugins/Employee/updateEmployee
- update employee/FlexiCore/rest/plugins/Industry/getAllIndustries
- getting all industries/FlexiCore/rest/plugins/Industry/createIndustry
- create industry/FlexiCore/rest/plugins/Industry/updateIndustry
- update industry/FlexiCore/rest/plugins/SalesPerson/listAllSalesPersons
- get all salespersons/FlexiCore/rest/plugins/SalesPerson/createSalesPerson
- create a salesperson/FlexiCore/rest/plugins/SalesPerson/updateSalesPerson
- updates salesperson/FlexiCore/rest/plugins/SalesRegion/listAllSalesRegions
- getting all sales regions/FlexiCore/rest/plugins/SalesRegion/createSalesRegion
- create a sales region/FlexiCore/rest/plugins/SalesRegion/updateSalesRegion
- update a sales region/FlexiCore/rest/plugins/Site/getAllSites
- getting all sites/FlexiCore/rest/plugins/Site/createSite
- create site/FlexiCore/rest/plugins/Site/updateSite
- update site/FlexiCore/rest/plugins/Supplier/getAllSuppliers
- getting all suppliers/FlexiCore/rest/plugins/Supplier/createSupplier
- create supplier/FlexiCore/rest/plugins/Supplier/updateSupplier
- update supplier
or by using the matching services:
BranchService.getAllBranches
- getting all branchesBranchService.createBranch
- create branchBranchService.updateBranch
- update branchCustomerService.getAllCustomers
- getting all customersCustomerService.createCutomer
- create customerCustomerService.updateCustomer
- update customerEmployeeService.listAllEmployees
- getting all employeesEmployeeService.createEmployee
- create employeeEmployeeService.updateEmployee
- update employeeIndustryService.getAllIndustries
- getting all industriesIndustryService.createIndustry
- create industryIndustryService.updateIndustry
- update industrySalesPersonService.listAllSalesPersons
- getting all salespersonsSalesPersonService.createSalesPerson
- creating salespersonSalesPersonService.updateSalesPerson
- updates salespersonSalesRegionService.listAllSalesRegions
- getting all sales regionSalesRegionService.createSalesRegion
- create sales regionSalesRegionService.updateSalesRegion
- update sales regionSiteService.getAllSites
- getting all sitesSiteService.createSite
- create siteSiteService.updateSite
- update siteSupplierService.getAllSuppliers
- getting all suppliersSupplierService.createSupplier
- create supplierSupplier.updateSupplier
- update supplier
Class Diagram
5.8.6.Address Management
The Address Service supports managing address related objects:
Address,Country,City,Neighbourhood,State,Street
this section refers to the following software components:
- flexicore-territories-service
- flexicore-territories-model
Managing Address Objects
the following endpoints are used to list/create/update the address objects:
-
/FlexiCore/rest/plugins/Address/listAllAddresses
- lists addresses -
/FlexiCore/rest/plugins/Address/createAddress
- creates address -
/FlexiCore/rest/plugins/Address/updateAddress
- updates address -
/FlexiCore/rest/plugins/City/listAllCities
- lists cities -
/FlexiCore/rest/plugins/City/createCity
- create city -
/FlexiCore/rest/plugins/City/updateCity
- update city -
/FlexiCore/rest/plugins/Country/listAllCountries
- lists countries -
/FlexiCore/rest/plugins/Country/createContry
- create country -
/FlexiCore/rest/plugins/Country/updateCountry
- update country -
/FlexiCore/rest/plugins/Neighbourhood/listAllNeighbourhoods
- lists neighborhoods -
/FlexiCore/rest/plugins/Neighbourhood/createNeighbourhood
- creates neighborhood -
/FlexiCore/rest/plugins/Neighbourhood/updateNeighbourhood
- updates neighborhood -
/FlexiCore/rest/plugins/State/listAllStates
- lists states -
/FlexiCore/rest/plugins/State/createState
- creates state -
/FlexiCore/rest/plugins/State/updateState
- updates state -
/FlexiCore/rest/plugins/Street/listAllStreets
- lists streets -
/FlexiCore/rest/plugins/Street/createStreet
- creates street -
/FlexiCore/rest/plugins/Street/updateStreet
- updates street
or the following services:
-
AddressService.listAllAddresses
- lists addresses -
AddressService.createAddress
- creates address -
AddressService.updateAddress
- updates address -
CityService.listAllCities
- lists cities -
CityService.createCity
- create city -
CityService.updateCity
- update city -
CountryService.listAllCountries
- lists countries -
CountryService.createContry
- create country -
CountryService.updateCountry
- update country -
NeighbourhoodService.listAllNeighbourhoods
- lists neighborhoods -
NeighbourhoodService.createNeighbourhood
- creates neighborhood -
NeighbourhoodService.updateNeighbourhood
- updates neighborhood -
StateService.listAllStates
- lists states -
StateService.createState
- creates state -
StateService.updateState
- updates state -
StreetService.listAllStreets
- lists streets -
StreetService.createStreet
- creates street -
StreetService.updateStreet
- updates street
Class Diagram
5.8.7.Billing management
The Billing Service supports managing billing-related objects: Contract,BusinessService,ContractItem,Currency,Invoice,InvoiceItem,
.
PaymentMethod,PaymentMethodType,PriceList,PriceListToService
this section refers to the following software components:
- billing-service
- billing-model
Billing Service depends on Address Management and Organization management.
the service contains CRUD methods and REST endpoints for managing the mentioned objects above and does not contain any actual defrayment method.
Adding Payment Methods
developers are expected to create a model and a service plugins for the new payment method and:
- create a
PaymentMethodType
on startup (make it is created only once) with the relevant name and description. - extend
PaymentMethod
the entity with more specific properties required to implement the interfacing with the payment service. - add REST APIs or Dynamic invokers to create the
PaymentMethod
extender defined in phase 2. - implement
PaymentMethodService
pay method.
Using the other base objects such as Contract,BusinessService,ContractItem
the billing service will charge the customer at the required times.
5.8.8.Equipment and Products
The product (and equipment) plugin supports the management Equipment,Product,ExternalServer,ProdcutStatus
.
These services and models were designed mainly for an IoT system but necessarily so.
This system can be used, for example, to track equipment inventory. If model or services lack functionality, entity inheritance and new services injected with the existing ones may be used to extend the system herewith as required. This is the spirit of Flexicore.
this section refers to the following software component:
- product-service
- product-model
product service depends on Organization management and Address Management.
Concepts
Equipment may have an external server that communicates with the equipment, in such case such server may be connected to Flexicore based server via the external server API. Such support is normally implemented through another plugin.
Alternatively, a local Flexicore server (can be regarded as a Gateway) may be connected directly to instances of equipment and synchronize its data and state with a 'main' Flexicore based server.
The product service supports the inspection of an external server for updates for the already existing equipment instances, this is used if the source of the status of this equipment is that external server, product service will automatically update the connectivity status of equipment instance by changing their status to "Connected" or "Disconnected" respectively.
The instances of the equipment may be periodically imported from the external server online or through some other import method. This is usually done as a method in an invoker or an explicit API. An explicit API cannot be called from a schedule instance. The invoker method is always preferred.
Managing Equipment
the product service supports CRUD methods for equipment, product, external server, and product status.
Other Common Services
Equipment is an entity which optionally has latitude and longitude, some of the common services product-service supports are:
-
plugins/Equipments/getAllEquipmentsGrouped
- returns equipment grouped by Geohash precision. -
plugins/Equipments/getProductGroupedByStatusAndType
- returns equipment grouped byProductStatus
andProductType
.
Adding New External Server Type Support
adding support for new external server type is done by implementing service and model plugins and:
- extending
ExternalServer
adding specific properties required for starting connection. - extending
ConnectionHolder
adding specific properties required to maintaining the connection. - registering an external server connection by firing the event
ExternalServerConnectionConfiguration
implementing the following methods:-
onConnect
- called perExternalServer
of the extended type (in phase 1) returns the extendedConnectionHolder
from phase 2. -
onRefresh
- called to fetch extenders ofExternalServer
(from phase 1) -
onInspect
- called perExternalServer
of the extended type (in phase 1) returns the extendedConnectionHolder
from phase 2. -
onInspect
- called perExternalServer
of the extended type (in phase 1) returns the extendedConnectionHolder
from phase 2. -
onSingleInspect
(optional) - will be called whenSingleInspectJob
(which is intended to inspect only a single Equipment) is fired.
-
5.8.9.Dynamic User Interface
FlexiCore allows developers to create a dynamic UI in a variety of ways, the main goals of dynamic UI are:
- reducing the necessity of changing the UI client-side code when new server-side features are implemented.
- eliminate the need to support multiple versions of the UI for multiple clients.
5.8.9.1.Main workflows and participants
Introduction
The important concepts used in the FlexiCore dynamic User Interface are:
- When the backend and frontend technologies are related, that is, 'knowledge of each other' better implementation can be achieved.
- Many Developers User Interface tasks are repeated and similar.
- Changes in a system are best implemented in one step.
- Drawing from the concept of 'application generators', people involved in a system lifetime include knowledgeable users capable of creating objects for other users. Under this paradigm, a superuser replaces the front-end developer.
Actors ( people related to the system)
Backend developer
The backend developer is responsible for creating new services and domain models at the back-end using FlexiCore plugins. The back-end developer decides on the API endpoints added, the Request and Response objects, and domain model additions.
Front-End Developer, hardly needed.
The front-end developer is responsible for supporting the dynamic UI services and widgets available in the proper SDK (Angular or Dart Flutter), this is done once and no changes are required when the backend exposes new features.
Superuser
The superuser is responsible for creating new objects such as Gridpresets, Trees, Dashboard Preset, Rules, etc. This is carried out using point and click without programming, it simply requires better knowledge of the system features.
Once a new service exposes new APIs on the back-end, the superuser can use them on the user interface without any intervention from the front-end developer side. This is true even when new APIs are available. So, for example, when a new data source (see below) becomes available the superuser can create a new Datagrid showing a new type of data sets so users can view the data and apply sorting and filtering based on the superuser Datagrid (gridpreset) design.
User
Users interact with the system without knowing how user interface widgets were created, by the user interface developer or by a superuser.
Workflows
By the User-Interface Developer
implement support for dynamic UI widgets use, creation, and editing.
The user interface (front-end developer) implements dynamic user interfaces to be used by all users and superusers. This can be carried out by either using the available back-end APIS available for supporting the lifecycle of dynamic UI classes or by using the lately developed client-side SDK component and services for saving a great deal of time in the implementation of a dynamic user interface support.
Not all implementation must include the editing side used by superusers, for example, the definition of a grid preset can be implemented on the browser only (Angular, Flutter). On mobile devices, implementation can include grid preset viewing and filtering only. This is a design decision.
By the back-end developer
The back-end developer doesn't have to change the standard implementation of services and models. The only additional, optional work is adding some metadata to methods (endpoints) and fields so dynamic user interface implementors can rely on them for more accurate implementation.
For example, if a method parameter field is defined as int and has max and min values, the user interface implementation can use a slide widget for such fields, likewise, the 'possible values' field in a parameter definition is of a type String and has a set of possible values, a drop-down can be implemented.
By Superusers
Superusers/administrators are responsible for the creation of usable instances based on the backend implementation.
Subtask: Define a data source (a Dynamic Execution using listAll method)
Superusers define a data source by selecting an Invoker from a list or by searching for the handling type an invoker supports. When a data source is defined, superusers can add optional filtering. This is similar in concept to views in SQL database.
Once a data source is defined, it can be reused for the creation of Trees, Grid Presets, Rules, and Dashboards. The definition of a datasource is saved in the database.
notes:
- The term datasource is used here for brevity. The backend creates a Dynamic Execution instance in the database for all InvokerMethods+Filtering saved in the database. When looking for datasource, an easy to use creation user interface should hide methods that are not listAll/
- Data sources are created and edited using the frontend user interface. Wizzdi provides client-side SDK and Angular services to make the implementation of data sources' creations a straightforward task, done once for each front-end browser application (by a front end developer)
- A sample application for datasources definition is available.
Subtask: create or edit a Datagrid from a Datasource (DynamicExecution)
Superusers can define datagrids along with these options:
- select a datasource from the available datasources (DynamicExecutions)
- select the visible fields from the typical response (a JSON object), associate the fields with columns
- define columns' width allocation
- define additional filters (reducing dataset size as is governed by the original filter the datasource may have).
- define run-time filters users will be able to use when viewing data.
- define access permissions to Grid Presets
- Link subgrids to a grid.
- Grids can be used from a selected row on a parent grid, In this case, filtering is narrows to reflect the selected row. For example,
- the parent grid shows customers.
- sub-grids show
- sales
- support calls
- orders
- the sub-grids are always filtered by customer id.
- Grids can be used from a selected row on a parent grid, In this case, filtering is narrows to reflect the selected row. For example,
notes:
- Datagrid presets (called Gridpreset) are saved to the database.
- As all other objects are, Datagrid Presets are subjected to the FlexiCore access control system.
Subtask: create or edit Hierarchical view from multiple datasources.
Subtask: create or edit a Dashboard preset from multiple datasources.
Subtask: create or edit Rules multiple datasources and triggers.
Subtask: Associate available user interface buttons with specific dynamic objects.
By Users
Users can view the available dynamic objects created for them that they are allowed to see and use, such dynamic objects can be available from a list per type, for example, a list of grid presets.
When a dynamic object is associated with a user interface element, then users can view the data when these elements interact with.
5.8.9.2.UIComponents
FlexiCore's UIComponent
leverages FlexiCore's out of the box state of the art permission system and access control and allows the UI to register objects reflecting some of the UI elements on the screen or some front-end property or action, then an administrator can allow/deny this UIComponent
for certain users, roles or tenants via the Access Control Management System. the UI client-side code may change the UI behavior(for example hiding it ) if the UIComponent
is denied for the user.
When UI components are not used, and all users see the same user interface, operations blocked by the server will fail with a server-side error (Forbidden 403), UIcomponents allow a cleaner behavior where some users should be blocked from accessing an operation.
API and workflow
using REST endpoint:
/FlexiCore/rest/uiPlugin/registerAndGetAllowedUIComponents
- receives a body that contains a list of UI components for each:
- name - the name of the UI component, used to better identify the
UIComponent
in the Access Control Management System - description - the description of the UI component, used to better identify the
UIComponent
in the Access Control Management System - externalId - the client-side code identifier for this UI component.
UI components that do not exist ( identified by externalId property) are created, the response of the API will be a list of allowed UI components which is a subset of the UI components provided to the API.
The interpretation of what to do when a UI component is available or not is solely a client-side code decision, common use cases are:
- Make a widget invisible/visible.
- Make a widget enabled/disabled (if both visible/invisible and enabled/disabled are required, create two UIComponents).
- Create a completely different panel or dialog for that user.
or by using the service:
UIComponentService.registerAndGetAllowedUIComponents
- receives a body that contains a list of ui components for each:
- name - the name of the UI component, used to better identify the
UIComponent
in the Access Control Management System - description - the description of the UIcomponent, used to better identify the
UIComponent
in the Access Control Management System - externalId - the client-side code identifier for this UI component
UI components that do not exist ( identified by externalId property) are created, the response of the API will be a list of allowed UI components which is a subset of the UI components provided to the API.
the usage flow of UIComponent
feature is usually as follows:
- The client-side designer identifies components he would like to hide/show based on the logged-in user.
- The client-side designer assigns externalID , name, and description for each component from phase 1.
- The client-side code calls
/FlexiCore/rest/uiPlugin/registerAndGetAllowedUIComponents
which returns theUIComponent
the logged-in user is allowed to access. - The client-side shows/hides relevant UI based on phase 3. However, the interprtaion of the existence of the UIComponent in the returned response for the current user is totally up to the client code.
5.8.9.3.UI Presets
the ui preset feature is used to describe a set of frequently used ui components allowing the a managing user to define and customize them.
a managing user is NOT a developer , this allows the system to expose features without any development requirement ( once the initial support for ui preset is done)
Note that ui preset feature highly relies on Dynamic invokers.
the plugins used by the ui preset feature are:
- flexicore-ui-service
- flexicore-ui-model
setting the priority of preset is a common service for all preset types (listed below) and it possible by using the following endpoints:
-
/FlexiCore/rest/plugins/UiFields/linkPresetToUser
- links preset with user providing:-
presetId
- the id of the preset to set the link for. -
userId
- the user id to set the link for. -
priority
- priority of the preset for this user , lower value indicates higher priority. -
enabled
- if this preset is enabled for this user.
-
/FlexiCore/rest/plugins/UiFields/linkPresetToRole
- links preset with user providing:-
presetId
- the id of the preset to set the link for. -
roleId
- the role id to set the link for. -
priority
- priority of the preset for this role , lower value indicates higher priority. -
enabled
- if this preset is enabled for this role .
-
/FlexiCore/rest/plugins/UiFields/linkPresetToTenant
- links preset with user providing:-
presetId
- the id of the preset to set the link for. -
tenantId
- the tenant id to set the link for. -
priority
- priority of the preset for this tenant, lower value indicates higher priority. -
enabled
- if this preset is enabled for this tenant.
-
5.8.9.3.1.Grid preset
GridPreset
is an entity that includes a DynamicExecution
and UI configuration string.
GridPreset
and the services supporting it are allowing a managing user to create grids for dynamic data (the data source must support invokers as defined in Dynamic invokers Section).
The grid preset supports column management, run time filtering and sorting as well as CSV export of the filtered grid data.
Creating a Grid
- client-side code should create a
DynamicExecution
as described in Dynamic invokers section. - client-side code should create a
GridPreset
entity by calling/FlexiCore/rest/plugins/GridPresets/createGridPreset
providing the created dynamic execution from phase 1.
Customizing the Grid
Once a grid is created it is possible to select the visible fields in the grid of the returned object by creating the TableColumn
entity with the following properties:
- name - must be the nested JSON path (dot separated) inside the returned object.
- sortable - if the field is sortable or not at runtime (when the grid is displayed).
- filterable - if the field is filterable.
- defaultColumnWidth - default column width when opening the presenting the preset. This is better provided in percents, the total width of all columns can exceed 100% as a horizontal scroll bar can be provided by the client-side.
- presetId - the id of the preset this
TableColumn
is connected to. - priority - used to determine the order of the columns.
- visible - if the column is visible or not
- dynamicField - if the column described a schema specified property or Dynamic Property.
- categoryName - the name of the category, this allows client-side to group columns by category.
- display name - name to display for the column.
5.8.9.3.2.Tree Preset
the Tree component allows managing Tree
,TreeNode
and TreeNodeToUser
.
Tree component is implemented by the following software components:
- tree-model
- tree-service
The tree component is used to describe nested lists of objects in a way that allows the UI presenting it to be unaware of the nature of the data it is presenting , this leverages Dynamic invokers. this is used instead of the traditional practice of crafting tailor made trees for each purpose.
Concepts
there are two phases when using the tree component the first one is the design phase the second one is the runtime phase. at design time the designer adds removes and updates tree and tree nodes , at runtime a designated UI will present the tree data based on the nodes created at design time, this includes presenting static nodes and presenting dynamic nodes which will fetch data from the server.
Managing Tree and Tree Nodes
create update and list Tree
entity using the following endpoints:
-
/FlexiCore/rest/plugins/tree/getAllTrees
- returns a list ofTree
objects -
/FlexiCore/rest/plugins/tree/createTree
- createsTree
using the following fields-
rootId
- id of theTreeNode
which is the root of the tree.
-
-
/FlexiCore/rest/plugins/tree/updateTree
- updatesTreeNode
create update and list TreeNode
entity using the following endpoints:
-
/FlexiCore/rest/plugins/treeNode/getAllTreeNodes
- returns a list ofTreeNode
objects -
/FlexiCore/rest/plugins/treeNode/createTreeNode
- createsTreeNode
using the following fields-
parentId
- id of theTreeNode
which is the parent of the new node or null if thisTreeNode
has not parent. -
dynamicExecutionId
- id of theDynamicExecution
that should be executed to fetch the dynamic data for this node or null if this is a static node. -
eager
- true or false to denote if the values for this tree node should be obtained eagerly or lazily. -
staticChildren
- true or false to denote if this tree node is dynamic or static -
invisible
- true or false to denote if this tree node is visible or not. -
allowFilteringEditing
- true or false to denote if ui should be presented to users allowing to change parameters used to fetch this tree node dynamic data. -
inMap
- true or false if the data for this tree node should be presented on a map rather than on the tree. -
presenterId
- id of thePreset
to present dynamic data on if presenting on tree is undesirable. -
iconId
- id of theFileResource
which is an icon for thisTreeNode
-
-
/FlexiCore/rest/plugins/treeNode/updateTreeNode
- updatesTreeNode
managing TreeNode
current open/close status for users is done using the following endpoints:
-
/FlexiCore/rest/plugins/treeToUser/saveTreeNodeStatus
- saves the current state for tree nodes for the current user, requries the following fields:nodeStatus
- a json object where the key is the id of theTreeNode
and the value is true/false with respect to it being open/close
-
/FlexiCore/rest/plugins/treeToUser/getTreeNodeStatus
- returns a list of tree nodes open for the current user.
5.8.9.3.3.Dashboard Preset
5.8.9.4.UI Plugins
the UI Plugin component allows managing UIPlugin
objects.
UI Plugin component is implemented by the following software components:
- ui-plugin-model
- ui-plugin-service
The UI Plugin component is used to manage dynamic UI components which will be dynamically used by the client to present any matching desirable content.
Concepts
UIPlugin
contains a FileResource
which should be a single file containing all the required javascript html and css to present the component.
Managing UI Plugins
create update and list UIPlugin
entity using the following endpoints:
-
/FlexiCore/rest/plugins/UIPlugins/getAllUIPlugins
- returns a list ofUIPlugin
objects -
/FlexiCore/rest/plugins/UIPlugins/UIPluginCreate
- createsUIPlugin
using the following fields-
contentId
- id of theFileResource
which contains the javascript,html and css data. -
version
- version of theUIPlugin
-
associationReference
- string used to determine when the usage of this ui plugin is possible
-
-
/FlexiCore/rest/plugins/UIPlugins/UIPluginUpdate
- updatesUIPlugin
5.8.10.Localization
The Localization Service supports translating server-side and client-side objects.
this section refers to the following software components:
- translation-service
- translation-model
Concepts
Localization Service revolves around the Translation
object which has an externalId
field or translated
field which is of type Baseclass and a languageCode
( this is determined by the creator of the translation object but usually it should be ISO 639-1 ). for translating server-side objects the client should provide the Baseclass
ids to translate , to translate client-side objects the client should provide externalId which is a unique id referring to the client-side object to be translated.
using plain i18n to describe server side objects is unrealistic as it means the i18n file has to include all of the described entities which makes it harder to manage and maintain.
Example Workflow
assuming we have a list of countries with a field called name which contains the name of the country and the values are in English and we would like to present them in Spanish , the client side workflow is as follows:
- fetch country entities as you normally would
- load i18n "file" from the url
/FlexiCore/rest/plugins/Translations/generateI18nJsonAll/{langCode}
providing "es" as langCode
Managing Translation objects
creating updating or listing translation objects is possible using the following endpoints:
-
/FlexiCore/rest/plugins/Translations/getAllTranslations
- getting the translation objects -
/FlexiCore/rest/plugins/Translations/updateTranslation
- updating translation objects -
/FlexiCore/rest/plugins/Translations/createTranslation
- creating translation object
or using the following TranslationService
methods
-
getAllTranslations
- getting the translation objects -
updateTranslation
- updating translation objects -
createTranslation
- creating translation object
I18n Support
the translation service supports importing/exporting from/to i18n format using the following endpoints:
-
/FlexiCore/rest/plugins/Translations/generateI18nJson
- accepts a filter body and returns a json in i18n format -
/FlexiCore/rest/plugins/Translations/generateI18nJsonAll/{langCode}
- receives the required langCode as parameter , this api is usfull when client requires a GET HTTP request -
/FlexiCore/rest/plugins/Translations/importI18n
- imports translation objects from i18n files
or via the following service methods:
-
generateI18nJson
- accepts a filter and returns a json in i18n format -
importI18n
- imports translation objects from i18n files
5.8.11.Licensing
FlexiCore allows plugin developers to restrict features of the system only to allowed customers. the license is valid only for the given computer using network interfaces MAC address, external hardware id, or disk serial number.
Licenses are provided per tenant, thus, a business-to-business system can provide additional paid-for features to some of its customers.
Configuring Licensed Components
developer should annotate its REST(or WebSocket or invoker) class with @OperationsInside
and specify the features
property for the features that are required to be licensed by the calling user, for each feature provide:
- canonicalName - this is the feature's unique identifier
- productCanonicalName - this is the feature's related product canonical name
note that each product consists of one or more features so a user may have a license for a specific feature or for the whole product.
a feature must be annotated with
@ProtectedREST
to be enforced.
Configuring Licensed Entities
Sometimes quantity based license is required (for example limiting created users to 50), FlexiCore allows enforcing by specifying the limitation when signing/requesting a license request.
Listing Licensing Products and Features
FlexiCore exposes the available license features and products using the following endpoints:
-
/FlexiCore/rest/licensingProducts/getAllLicensingProducts
- lists licensing products /FlexiCore/rest/licensingFeatures/getAllLicensingFeatures
- lists licensing features
Requesting a License
The user should create a LicenseRequest
object and attach the requested features and products by creating LicenseRequestToFeature,LicenseRequestToQuantityFeature
and LicenseRequestToProduct
using the following endpoints:
/FlexiCore/rest/licenseRequests/createLicenseRequest
- creates a license request/FlexiCore/rest/licenseRequestToFeatures/createLicenseRequestToFeature
- attaches a license feature to a license request/FlexiCore/rest/licenseRequestToProducts/createLicenseRequestToProduct
- attaches a license product to a license request/FlexiCore/rest/licenseRequestToQuantityFeatures/createLicenseRequestToQuantityFeature
- attaches a quantity license feature to a license request
Then the user should download the requestFile
in the LicenseRequest
(see File management support on how to download files) and provide it to a signing user (this is carried out on a different system in most cases).
Signing a License request
using the license request signer tool the signing user should do as follows to sign a license request:
- create public/private keys or load existing ones
- load the request to sign - this is sent by the requesting user
- add quantity based licensing features
- sign the request
- send the certificate and the public key to the requester
Importing signed request
once the license granter approved the license request the license requester should:
- update the public key file (see Installing FlexiCore )
- update the license request creating
LicenseRequestToQuantityFeature
if this was added to the updated request file - upload the certificate file (see File management support on how to upload files)
- update the license request by calling
/FlexiCore/rest/licenseRequests/updateLicenseRequest
providing the certificateFileResource
id
5.8.12.Scheduling
the Scheduling component allows managing Scheduling objects, their time slots, and actions.
Scheduling component is implemented by the following software components:
- scheduling-model
- scheduling-service
Scheduling actions greatly relies on Dynamic invokers.
Concepts
Scheduling is used to time one or more actions (for example sending mail or fetching data from remote server) in one or more time slots.
selecting an action is possible from the already implemented dynamic invokers or by adding a new dynamic invoker.
Managing Scheduling Entities
create update and list Schedule
entity using the following endpoints:
/FlexiCore/rest/plugins/Scheduling/getAllSchedules
- returns a list of schedules/FlexiCore/rest/plugins/Scheduling/createSchedule
- creates a schedule,main properties listed below:- name - The name of the schedule
- Sunday - Saturday - enabled for each day of the week
- holiday enabled for holidays
- timeFrameStart - the date the schedule will be valid from
- timeFrameEnd - the date the schedule will stop be valid from
/FlexiCore/rest/plugins/Scheduling/updateSchedule
- updates a schedule
or using the following service methods:
SchedulingService.getAllSchedules
- returns a list of schedulesSchedulingService.createSchedule
- creates a scheduleSchedulingService.updateSchedule
- updates a schedule
create update and list ScheduleTimeslot
entity using the following endpoints:
/FlexiCore/rest/plugins/Scheduling/getAllScheduleTimeslots
-returns a list of schedule time slot/FlexiCore/rest/plugins/Scheduling/createScheduleTimeslot
- creates a schedule time slot/FlexiCore/rest/plugins/Scheduling/updateSchedule
-updates a schedule time slot
or using the following service methods:
SchedulingService.getAllScheduleTimeslots
- returns a list of schedule time slotSchedulingService.createScheduleTimeslot
- creates a schedule time slotSchedulingService.updateScheduleTimeSlot
- updates a schedule time slot
create update and list ScheduleAction
entity using the following endpoints:
/FlexiCore/rest/plugins/Scheduling/getAllScheduleActions
- returns a list of schedule Actions/FlexiCore/rest/plugins/Scheduling/createScheduleAction
- creates a schedule Action/FlexiCore/rest/plugins/Scheduling/updateScheduleAction
- updates a schedule Action
or using the following service methods:
SchedulingService.getAllScheduleActions
- returns a list of schedule actionsSchedulingService.createScheduleAction
- creates a schedule , providing:- dynamicExecutionId- the id of the dynamic execution to use for this action
SchedulingService.updateScheduleAction
- updates a schedule action
linking/unlinking SchedulingAction
and Schedule
using the following endpoint:
/FlexiCore/rest/plugins/Scheduling/linkScheduleToAction
- links an action with a schedule/FlexiCore/rest/plugins/Scheduling/unlinkScheduleToAction
- unlinks an action with a schedule
or using the following service methods:
SchedulingService.linkScheduleToAction
-links action with a scheduleSchedulingService.unlinkScheduleToAction
- unlinks an action with a schedule
Class Diagram
`
5.8.13.Order Management
The order service plugin supports managing Orders.
The main use case here is tracking goods ordered from a supplier. Orders can be linked to a consuming organization, that is, the customer for this order.
this section refers to the following software components:
- order-service
- order-model
order service depends on Organization management , Address Management and Equipment and Products .
Managing Orders
order service manages Order,OrderItem,SupplyTime
entities.
Sending Order to Supplier
Sometimes it is required to send orders to suppliers for processing, this is done by creating a service and model plugins and follow the steps:
- create a supplier entity representing the supplier you connect to.
- when creating the order set the supplier to the one created in phase 1.
- implement a logic that will send the order and implement (for example Scheduling)
5.8.14.WhatsApp Integration
the WhatsApp component allows managing WhatsApp servers objects with the required credentials to send basic WhatsApp messages
WhatsApp component is implemented by the following software components:
- whatsapp-model
- whatsapp-service
Managing WhatsApp Servers
create update and list WhatsappServer
entity using the following endpoints:
-
/FlexiCore/rest/plugins/whatsappServer/getAllWhatsappServers
- returns a list of WhatsApp Servers -
/FlexiCore/rest/plugins/whatsappServer/createWhatsappServer
- createsWhatsappServer
using the following fields-
twilioAccountSid
- twillio account sid obtained from twillio -
authenticationToken
- authentication token obtained from twillio
-
-
/FlexiCore/rest/plugins/whatsappServer/updateWhatsappServer
- updatesWhatsappServer
sending a basic WhatsApp message is available using the API:
/FlexiCore/rest/plugins/whatsappServer/sendWhatsappMessage
with the following parameters:
sendFrom
- whatsapp number to send fromsendTo
- whatsapp number to send to-
whatsAppServerId
- id of theWhatsappServer
to use content
- message content
5.8.15.SendGrid Integration
the Sendgrid component allows managing Sendgrid servers objects with the required credentials to send emails.
Sendgrid component is implemented by the following software components:
- sendgrid-model
- sendgrid-service
Managing SendGrid Servers
create update and list SendGridServer
entity using the following endpoints:
-
/FlexiCore/rest/plugins/SendGridServer/getAllSendGridServers
- returns a list of SendGrid Servers -
/FlexiCore/rest/plugins/SendGridServer/createSendGridServer
- createsSendGridServer
using the following fields-
apiKey
- SendGrid Api Key
-
-
/FlexiCore/rest/plugins/SendGridServer/updateSendGridServer
- updatesSendGridServer
create update and list SendGridTemplate
entity using the following endpoints:
-
/FlexiCore/rest/plugins/SendGridTemplate/getAllSendGridTemplates
- returns a list of SendGrid Templates -
/FlexiCore/rest/plugins/SendGridTemplate/importSendGridTemplates
- imports a list of SendGrid templates from SendGrid using the following fields-
sendGridServerId
- id of theSendGridServer
to import templates for
-
sending an email is possible using the API:
/FlexiCore/rest/plugins/SendGridServer/sendMail
with the following parameters:
sendGridTemplateId
- id of theSendGridTemplate
to use when sending the mailemailFrom
- email to send the mail from-
emailRefs
- list of emails to send mail to - dynamic data - any added json fields will be sent to SendGrid as parameters that will be used by the template
5.9.Swagger
FlexiCore has out of the box support for Swagger .
FlexiCore protects the swagger UI and only authorized users are allowed to access it , the swagger interface is available at /FlexiCore/index.html
5.10.Front End SDK
Below there are a list of libraries already implementing FlexiCore's API for easier use.
5.10.2.Flutter compatible FlexiCore SDK
The Dart SDK for Flutter is available upon request.
5.11.Deploying Flexicore Applications and Systems
5.12.Build Tools plugins
FlexiCore provides a maven plugin to fix issues with meta-model generation between entity plugins.
<plugin> <groupId>com.wizzdi</groupId> <artifactId>metamodel-inheritence-fix-processor</artifactId> <version>1.0.1</version> <executions> <execution> <id>flexicore</id> <phase>process-sources</phase> <goals> <goal>process</goal> </goals> </execution> </executions> </plugin>