Copyright © 2015-2020
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
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.
this documentation structure was inspired by Spring Boot Documentation.
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.
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.
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.
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
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.
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.
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:
@GET,@Produces,@Consumes,@Path,@HeaderParam,@Context
annotations)@Extension
annotation , this will add this class to PF4J's extensions.idx file.@Tag
annotation@Primary,@Component
more info in spring docs @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 descriptionat 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
.
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.
FlexiCore Provides a unique set of features in many domains and fields, those are described in the following sections.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
FlexiCore supports most of spring capabilities in its plugins , writing service or model type plugin is described in the sections below.
the common solutions when designing a software system are:
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.
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 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.
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).
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.
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:
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:
.... <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 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.
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 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 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; }
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.
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.
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.
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.
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:
<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.
<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:
In Linux, this is by default: /home/flexicore/plugins and is defined in Spring application.properties file.
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); }
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.
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
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) { } }
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); } }
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 :
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.
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.
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
Flexicore access control system makes use of the following Entities:
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:
Baseclass
.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.
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.
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.
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.
When a REST call is invoked, the system checks the following:
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.
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
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:
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.
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.
See relevant APIs and the system provided user interface for managing permissions.
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); }
Dynamic Invokers are services which 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 :
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
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 a CoffeeMaker specific parameters which will again require client-side code
each plugin will implement an invoker with a method called makeCoffee , a common object 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
invoker is a Service Plugin which 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 which references a specific type (this allows 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 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); } }
/rest/DynamicInvokers/getAllInvokers
the response will contain a list of invoker classes ,thier methods and the parameters for each methodidRefType
) a selection will be shown from the result of the listAll invoker method for that given type (in our example when handling the field coffeeMachineId
clients side will attempt to find an invoker which has a handlingType
of com.flexicore.examples.service.CoffeeMachine
with a listAll
method)/rest/dynamicInvokers/executeInvoker
providing:
invokerNames
- canonical names for the relevant invoker classes obtained from phase 1 (in our example its com.flexicore.examples.service.CoffeeMakerAInvoker
)invokerMethodName
- (the method on the invoker(s) to call , in our example its makeCoffee
)executionParametersHolder
- the parameters for the method including a type
field with the canonical name of the request object (in our example its com.flexicore.examples.service.MakeCoffeeRequest
)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
/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
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 md5offset
- the file resource current offsetdone
- true/false if the file upload was completedkeepUntil
- if set FlexiCore will delete this file once keepUntil
date has passedoriginalFilename
- 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:
FlexiCore/rest/resources/{md5}
will return a file resource object with the given md5 or empty response if non existsFlexiCore/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 filename
filename ( this will be saved as originalName
on FileResource
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 changesFlexiCore/rest/resources/{md5}
to get the current file offset, if the offset equals the file size, there is no need to upload it.FlexiCore/rest/resources/upload
API, update offset counter from returned FileResource
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
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
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).
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 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
most FlexiCore APIs expect the authentication token to be provided as a header named authenticationKey
@ProtectedREST
@Operation
annotation or FlexiCore's @Read,@Write,@Update,@Delete
SecurityContext
annotated by @Context
an example can be found in Writing The Code section
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
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.
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.
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
FlexiCore provides a set of plugins out of the box which expands the capabilities of the system.
The IoT Service supports sending/receiving messages to/from a remote FlexiCore server.
this section refers to the following software components:
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:
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.
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.
FlexiCore allows creating updating and listing FlexiCore servers using the following endpoints:
/FlexiCore/rest/flexicoreServer/createFlexiCoreServer
- creates a FlexiCoreServer
/FlexiCore/rest/flexicoreServer/registerFlexiCoreServer
- when an unknown FlexiCoreServer
attempts connecting to our FlexiCoreServer
an instance of UnRegisteredFlexiCoreServer
will be created and the connection will be closed. this api allows registering one of those UnRegisteredFlexiCoreServer
as a proper FlexiCoreServer
./FlexiCore/rest/flexicoreServer/updateFlexiCoreServer
- updates a FlexiCoreServer
/FlexiCore/rest/flexicoreServer/getFlexiCoreServers
- lists FlexiCoreServer
sor using the following service methods
FlexiCoreServerService.createFlexiCoreServer
- creates a FlexiCoreServer
FlexiCoreServerService.registerFlexiCoreServer
- when an unknown FlexiCoreServer
attempts connecting to our FlexiCoreServer
an instance of UnRegisteredFlexiCoreServer
will be created and the connection will be closed. this api allows registering one of those UnRegisteredFlexiCoreServer
as a proper FlexiCoreServer
.FlexiCoreServerService.updateFlexiCoreServer
- updates a FlexiCoreServer
FlexiCoreServerService.getFlexiCoreServers
- lists FlexiCoreServer
sremote 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 FlexiCoreIOTRequest
or extenders is possible using IOTService.sendRequest(communicationId,flexiCoreExecutionRequest,c,callback)
where :
communicationId
- remote FlexiCoreServer
externalIdflexiCoreExecutionRequest
- request to sendc
- type for the expected callbackcallback
- callback that will be called once a response for the sent request is receivedservices 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 receivedHealthUpdateRequest
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
developers may extend FlexiCoreIOTRequest
and FlexiCoreIOTResponse
to send custom IOT requests and responses
Synchronization Service allows syncing entities between FlexiCore servers.
this section refers to the following software components:
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.
Baseclass
Entity to be Synced with remote FlexiCore Serverdevelopers need to create a FlexiCoreServerToBaseclass
using the endpoint:
FlexiCore/rest/plugins/flexiCoreServerToBaseclass/createFlexiCoreServerToBaseclass
- providing the FlexiCoreServer
's id and the entity to be synced idor using the service
FlexiCoreServerToBaseclassService.createFlexiCoreServerToBaseclass
- providing the FlexiCoreServer
and the entity to be syncedonce this link is created the given entity will be synced to remote FlexiCore Server if it is ever updated
BaseclassNoSQL
Entity to be Synced with remote FlexiCore Serverdevelopers need to create a NoSQLSyncLink
using the endpoint:
FlexiCore/rest/plugins/noSQLSyncLink/createNoSQLSyncLink
- providing the FlexiCoreServer
's id and the entity to be synced idor using the service
NoSQLSyncLinkService.createNoSQLSyncLink
- providing the FlexiCoreServer
and the entity to be syncedonce this link is created the given entity will be synced once and the link will be later deleted
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 receivedSyncService.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 objectregisterCompareCallback(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 nullsometimes 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.
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:
the software update mechanism relies on Multi-node synchronization and IOT
Software update service manages SoftwareUpdate,SoftwareUpdateBundle,SoftwareUpdateInstallation
:
SoftwareUpdate
- an update of the following types:
SoftwareUpdateBundle
- a package of multiple SoftwareUpdate
SoftwareUpdateInstallation
- an installation request of a SoftwareUpdateBundle
on a FlexiCoreServer
( for more on FlexiCoreServer
see Multi-node synchronization and IOT) , it contains the current status of installation and any output received while running the installationinstalling software on remote FlexiCore server sequence:
/FlexiCore/rest/plugins/SoftwareUpdate/createSoftwareUpdate
SoftwareUpdateBundle
by calling /FlexiCore/rest/plugins/SoftwareUpdateBundle/createSoftwareUpdateBundle
/FlexiCore/rest/plugins/UpdateToBundle/createUpdateToBundle
SoftwareUpdateInstallation
by calling /FlexiCore/rest/plugins/SoftwareUpdateInstallation/createSoftwareUpdateInstallation
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.
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.
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 depends on Address Management component.
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 supplieror 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 supplierThe Address Service supports managing address related objects:
Address,Country,City,Neighbourhood,State,Street
this section refers to the following software components:
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 streetor the following services:
AddressService.listAllAddresses
- lists addressesAddressService.createAddress
- creates addressAddressService.updateAddress
- updates addressCityService.listAllCities
- lists citiesCityService.createCity
- create cityCityService.updateCity
- update cityCountryService.listAllCountries
- lists countriesCountryService.createContry
- create countryCountryService.updateCountry
- update countryNeighbourhoodService.listAllNeighbourhoods
- lists neighborhoodsNeighbourhoodService.createNeighbourhood
- creates neighborhoodNeighbourhoodService.updateNeighbourhood
- updates neighborhoodStateService.listAllStates
- lists statesStateService.createState
- creates stateStateService.updateState
- updates stateStreetService.listAllStreets
- lists streetsStreetService.createStreet
- creates streetStreetService.updateStreet
- updates street
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 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.
developers are expected to create a model and a service plugins for the new payment method and:
PaymentMethodType
on startup (make it is created only once) with the relevant name and description.PaymentMethod
the entity with more specific properties required to implement the interfacing with the payment service.PaymentMethod
extender defined in phase 2.PaymentMethodService
pay method.Using the other base objects such as Contract,BusinessService,ContractItem
the billing service will charge the customer at the required times.
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 depends on Organization management and Address Management.
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.
the product service supports CRUD methods for equipment, product, external server, and product status.
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 by ProductStatus
and ProductType
.adding support for new external server type is done by implementing service and model plugins and:
ExternalServer
adding specific properties required for starting connection.ConnectionHolder
adding specific properties required to maintaining the connection.ExternalServerConnectionConfiguration
implementing the following methods:
onConnect
- called per ExternalServer
of the extended type (in phase 1) returns the extended ConnectionHolder
from phase 2.onRefresh
- called to fetch extenders of ExternalServer
(from phase 1)onInspect
- called per ExternalServer
of the extended type (in phase 1) returns the extended ConnectionHolder
from phase 2.onInspect
- called per ExternalServer
of the extended type (in phase 1) returns the extended ConnectionHolder
from phase 2.onSingleInspect
(optional) - will be called when SingleInspectJob
(which is intended to inspect only a single Equipment) is fired.
FlexiCore allows developers to create a dynamic UI in a variety of ways, the main goals of dynamic UI are:
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.
using REST endpoint:
/FlexiCore/rest/uiPlugin/registerAndGetAllowedUIComponents
- receives a body that contains a list of UI components for each:
UIComponent
in the Access Control Management SystemUIComponent
in the Access Control Management SystemUI 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:
or by using the service:
UIComponentService.registerAndGetAllowedUIComponents
- receives a body that contains a list of ui components for each:
UIComponent
in the Access Control Management SystemUIComponent
in the Access Control Management SystemUI 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:
/FlexiCore/rest/uiPlugin/registerAndGetAllowedUIComponents
which returns the UIComponent
the logged-in user is allowed to access.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:
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.
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.
DynamicExecution
as described in Dynamic invokers section.GridPreset
entity by calling /FlexiCore/rest/plugins/GridPresets/createGridPreset
providing the created dynamic execution from phase 1.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:
TableColumn
is connected to.
the Tree component allows managing Tree
,TreeNode
and TreeNodeToUser
.
Tree component is implemented by the following software components:
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.
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.
create update and list Tree
entity using the following endpoints:
/FlexiCore/rest/plugins/tree/getAllTrees
- returns a list of Tree
objects/FlexiCore/rest/plugins/tree/createTree
- creates Tree
using the following fields
rootId
- id of the TreeNode
which is the root of the tree./FlexiCore/rest/plugins/tree/updateTree
- updates TreeNode
create update and list TreeNode
entity using the following endpoints:
/FlexiCore/rest/plugins/treeNode/getAllTreeNodes
- returns a list of TreeNode
objects/FlexiCore/rest/plugins/treeNode/createTreeNode
- creates TreeNode
using the following fields
parentId
- id of the TreeNode
which is the parent of the new node or null if this TreeNode
has not parent.dynamicExecutionId
- id of the DynamicExecution
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 staticinvisible
- 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 the Preset
to present dynamic data on if presenting on tree is undesirable.iconId
- id of the FileResource
which is an icon for this TreeNode
/FlexiCore/rest/plugins/treeNode/updateTreeNode
- updates TreeNode
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 the TreeNode
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.the UI Plugin component allows managing UIPlugin
objects.
UI Plugin component is implemented by the following software components:
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.
UIPlugin
contains a FileResource
which should be a single file containing all the required javascript html and css to present the component.
create update and list UIPlugin
entity using the following endpoints:
/FlexiCore/rest/plugins/UIPlugins/getAllUIPlugins
- returns a list of UIPlugin
objects/FlexiCore/rest/plugins/UIPlugins/UIPluginCreate
- creates UIPlugin
using the following fields
contentId
- id of the FileResource
which contains the javascript,html and css data.version
- version of the UIPlugin
associationReference
- string used to determine when the usage of this ui plugin is possible/FlexiCore/rest/plugins/UIPlugins/UIPluginUpdate
- updates UIPlugin
The Localization Service supports translating server-side and client-side objects.
this section refers to the following software components:
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.
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:
/FlexiCore/rest/plugins/Translations/generateI18nJsonAll/{langCode}
providing "es" as langCodecreating 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 objector using the following TranslationService
methods
getAllTranslations
- getting the translation objectsupdateTranslation
- updating translation objectscreateTranslation
- creating translation object
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 filesor via the following service methods:
generateI18nJson
- accepts a filter and returns a json in i18n formatimportI18n
- imports translation objects from i18n filesFlexiCore 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.
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:
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.
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.
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 featuresThe 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 requestThen 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).
using the license request signer tool the signing user should do as follows to sign a license request:
once the license granter approved the license request the license requester should:
LicenseRequestToQuantityFeature
if this was added to the updated request file/FlexiCore/rest/licenseRequests/updateLicenseRequest
providing the certificate FileResource
id
the Scheduling component allows managing Scheduling objects, their time slots, and actions.
Scheduling component is implemented by the following software components:
Scheduling actions greatly relies on Dynamic invokers.
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.
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:
/FlexiCore/rest/plugins/Scheduling/updateSchedule
- updates a scheduleor using the following service methods:
SchedulingService.getAllSchedules
- returns a list of schedulesSchedulingService.createSchedule
- creates a scheduleSchedulingService.updateSchedule
- updates a schedulecreate 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 slotor 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 slotcreate 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 Actionor using the following service methods:
SchedulingService.getAllScheduleActions
- returns a list of schedule actionsSchedulingService.createScheduleAction
- creates a schedule , providing:
SchedulingService.updateScheduleAction
- updates a schedule actionlinking/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 scheduleor using the following service methods:
SchedulingService.linkScheduleToAction
-links action with a scheduleSchedulingService.unlinkScheduleToAction
- unlinks an action with a schedule
`
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 depends on Organization management , Address Management and Equipment and Products .
order service manages Order,OrderItem,SupplyTime
entities.
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:
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:
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
- creates WhatsappServer
using the following fields
twilioAccountSid
- twillio account sid obtained from twillioauthenticationToken
- authentication token obtained from twillio/FlexiCore/rest/plugins/whatsappServer/updateWhatsappServer
- updates WhatsappServer
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 towhatsAppServerId
- id of the WhatsappServer
to usecontent
- message contentthe Sendgrid component allows managing Sendgrid servers objects with the required credentials to send emails.
Sendgrid component is implemented by the following software components:
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
- creates SendGridServer
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 the SendGridServer
to import templates forsending an email is possible using the API:
/FlexiCore/rest/plugins/SendGridServer/sendMail
with the following parameters:
sendGridTemplateId
- id of the SendGridTemplate
to use when sending the mailemailFrom
- email to send the mail fromemailRefs
- list of emails to send mail toFlexiCore 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
Below there are a list of libraries already implementing FlexiCore's API for easier use.
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>