diff --git a/publish/404.html b/publish/404.html new file mode 100644 index 0000000..c5a8fa0 --- /dev/null +++ b/publish/404.html @@ -0,0 +1 @@ +
The Strolch API revolves around the StrolchTransaction object. The main +concept is to implement your use cases in Service implementations. You +open a transaction using the openTx(String)-method and then perform the +use case by adding your Command instances to the transaction.
Transactions are opened on a StrolchRealm. The realms are used to +separate mandates in a single runtime instance of Strolch. Each realm +has its own ResourceMap, OrderMap, ActivityMap instances from which the +TX retrieves the elements.
The Strolch model is implemented in the project li.strolch.model.
The Strolch model consists of three root level elements: Resource, +Order and Activity. Each element has at least the following attributes:
Each root element can have any number of ParameterBag instances on it, +which in turn can have any number of Parameters on it. Accessing these +objects is always done by their IDs. Strolch root elements are always +stored in the respective ElementMaps in their Strolch realm. Thus +accessing a certain parameter from a Resource would look like this:
try (StrolchTransaction tx = openTx(realmName)) {
+ Resource resource = tx.getResourceBy("TestType", "MyTestResource");
+ Date date = resource.getDate("@bag01", "@param6");
+ logger.info("@param6 date is " + date);
+}
+
XML Presentation of Strolch’s top level elements of Resources:
<!-- Resource instance -->
+<Resource Id="MyTestResource" Name="Test Name" Type="TestType">
+ <ParameterBag Id="@bag01" Name="Test Bag" Type="TestBag">
+ <Parameter Id="@param7" Name="StringList Param" Type="StringList" Value="Hello;World" />
+ <Parameter Id="@param6" Name="Date Param" Type="Date" Value="2012-11-30T18:12:05.628+01:00" />
+ <Parameter Id="@param5" Name="String Param" Type="String" Value="Strolch" />
+ </ParameterBag>
+ <ParameterBag Id="@bag02" Name="Test Bag" Type="TestBag">
+ <Parameter Id="@param4" Name="Long Param" Type="Long" Value="4453234566" />
+ <Parameter Id="@param3" Name="Integer Param" Type="Integer" Value="77" />
+ <Parameter Id="@param2" Name="Float Param" Type="Float" Value="44.3" />
+ <Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true" />
+ </ParameterBag>
+ <TimedState Id="@integerState" Name="Integer State" Type="IntegerState">
+ <Value Time="0" Value="1" />
+ <Value Time="1" Value="2" />
+ <Value Time="2" Value="3" />
+ </TimedState>
+</Resource>
+
XML Presentation of Strolch’s top level elements of Orders:
<!-- Order instance -->
+<Order Id="MyTestOrder" Name="Test Name" Type="TestType" Date="2013-11-20T07:42:57.699Z" State="CREATED">
+ <ParameterBag Id="@bag01" Name="Test Bag" Type="TestBag">
+ <Parameter Id="@param7" Name="StringList Param" Type="StringList" Value="Hello;World" />
+ <Parameter Id="@param6" Name="Date Param" Type="Date" Value="2012-11-30T18:12:05.628+01:00" />
+ <Parameter Id="@param5" Name="String Param" Type="String" Value="Strolch" />
+ </ParameterBag>
+ <ParameterBag Id="@bag02" Name="Test Bag" Type="TestBag">
+ <Parameter Id="@param4" Name="Long Param" Type="Long" Value="4453234566" />
+ <Parameter Id="@param3" Name="Integer Param" Type="Integer" Value="77" />
+ <Parameter Id="@param2" Name="Float Param" Type="Float" Value="44.3" />
+ <Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true" />
+ </ParameterBag>
+</Order>
+
XML Presentation of Strolch’s top level elements of Activities:
<!-- Activity instance -->
+<Activity Id="bicycleProduction" Name="Bicycle Production" Type="Series">
+
+ <Activity Id="componentProduction" Name="Production of components" Type="Series">
+
+ <Action Id="consumeGears" Name="Gears"
+ ResourceId="gears" ResourceType="Article" Type="Consume">
+ <ParameterBag Id="objectives" Name="Production goals" Type="Objectives">
+ <Parameter Id="quantity" Name="Quantity" Type="Float" Value="1" />
+ <Parameter Id="duration" Name="Duration" Type="Duration" Value="PT0S" />
+ </ParameterBag>
+ </Action>
+
+ <Activity Id="frameProduction" Name="Production frame" Type="Series">
+ <Action Id="produce" Name="Production frame"
+ ResourceId="frameProduction" ResourceType="Machine" Type="Use">
+ <ParameterBag Id="objectives" Name="Production goals" Type="Objectives">
+ <Parameter Id="quantity" Name="Quantity" Type="Float" Value="1" />
+ <Parameter Id="duration" Name="Duration" Type="Duration" Value="PT5M" />
+ </ParameterBag>
+ </Action>
+ <Action Id="toStock" Name="Frame ToStock"
+ ResourceId="frame" ResourceType="Article" Type="Produce">
+ <ParameterBag Id="objectives" Name="Production goals" Type="Objectives">
+ <Parameter Id="quantity" Name="Quantity" Type="Float" Value="1" />
+ <Parameter Id="duration" Name="Duration" Type="Duration" Value="PT1M" />
+ </ParameterBag>
+ </Action>
+ </Activity>
+
+ <Activity Id="brakeProduction" Type="Series" Name="Herstellen Bremsen" TimeOrdering="Series">
+ <Action Id="produce" Name="Production saddle"
+ ResourceId="saddleProduction" ResourceType="Machine" Type="Use">
+ <ParameterBag Id="objectives" Name="Production goals" Type="Objectives">
+ <Parameter Id="quantity" Name="Quantity" Type="Float" Value="1" />
+ <Parameter Id="duration" Name="Duration" Type="Duration" Value="PT5M" />
+ </ParameterBag>
+ </Action>
+ <Action Id="toStock" Name="Saddle ToStock"
+ ResourceId="frame" ResourceType="Article" Type="Produce">
+ <ParameterBag Id="objectives" Name="Production goals" Type="Objectives">
+ <Parameter Id="quantity" Name="Quantity" Type="Float" Value="1" />
+ <Parameter Id="duration" Name="Duration" Type="Duration" Value="PT1M" />
+ </ParameterBag>
+ </Action>
+ </Activity>
+
+ </Activity>
+
+ <Action Id="assembly" Name="Bicycle assemble"
+ ResourceId="bicycleAssembly" ResourceType="Assembly" Type="Use">
+ <ParameterBag Id="objectives" Name="Production goals" Type="Objectives">
+ <Parameter Id="quantity" Name="Quantity" Type="Float" Value="1" />
+ <Parameter Id="duration" Name="Duration" Type="Duration" Value="PT5M" />
+ </ParameterBag>
+ </Action>
+
+ <Action Id="toStock" Name="Bicycle to stock"
+ ResourceId="bicycle" ResourceType="Product" Type="Produce">
+ <ParameterBag Id="objectives" Name="Production goals" Type="Objectives">
+ <Parameter Id="quantity" Name="Quantity" Type="Float" Value="1" />
+ <Parameter Id="duration" Name="Duration" Type="Duration" Value="PT1M" />
+ </ParameterBag>
+ </Action>
+</Activity>
+
Strolch realms implement the multi-client capability which is thus baked right +into the Strolch runtime. When configuring a Strolch runtime, realms are +configured and for each realm the data store mode is set. Each realm has its +own persistence configuration and can thus run in one of the 4 modes that the +Strolch agent implements:
Strolch Realms are also responsible for opening Transactions, as these are +bound to the persistence layer configured for this realm. At runtime, a realm +is then accessed from the ComponentContainer:
ComponentContainer container = getAgent().getContainer();
+StrolchRealm realm = container.getRealm(StrolchConstants.DEFAULT_REALM);
+try(StrolchTransaction tx = realm.openTx()) {
+ Resource resource = tx.getResourceBy("TestType", "MyTestResource");
+ ...
+}
+
To start developing Strolch you need an installed:
Setting up Strolch is just a few lines:
git clone https://github.com/4treesCH/strolch.git
+cd strolch
+mvn clean install -DskipTests
+
Note: To run the tests you will need to configure the PostgreSQL Databases. See +the README in the module.
After running the Maven build, you will have a full build of all Strolch +projects. Now you can start modifying the projects, and add your own features, +or, far more interesting, start developing your projects using the Strolch +agent.
To create your own Strolch App, you can use Maven’s archetype generation. There +are two versions, one is a simple Java App which you can use to directly access +the Strolch runtime, and the second is to create a Java Web App, which is the +usual way to run Strolch runtimes.
Note: you need to have installed Strolch to your local maven repo, otherwise the +archetype won’t be available.
The following shows the maven command to create the new maven project. Note that you should replace the placeholders in the brackets:
mvn archetype:generate \
+ -DarchetypeGroupId=li.strolch \
+ -DarchetypeArtifactId=li.strolch.mvn.archetype.webapp \
+ -DarchetypeVersion=1.6.0-SNAPSHOT \
+ -DgroupId=<my.groupid> \
+ -DartifactId=<my-artifactId> \
+ -Dversion=<my.version> \
+ -DappName="<my app name>"
+
The Strolch Web App uses NodeJS v11.x to build the web dependencies. Please
+download the relevant platform’s package, unpack it, and add the bin
directory
+to your path variable.
Once NodeJS is installed, then you can prepare the web dependencies:
cd src/main/webapp/
+npm install gulp -g
+npm install
+gulp
+
Note: Whenever the bower.json is changed then you should +again call npm install inside the webapp folder.
Building the WAR uses the package
maven goal, but to have the optimized WAR
+use the release
profile:
mvn clean package -Prelease
+
Happy coding =))
The following shows the maven command to create the new maven project. Note that +you should replace the placeholders in the brackets:
mvn archetype:generate \
+ -DarchetypeGroupId=li.strolch \
+ -DarchetypeArtifactId=li.strolch.mvn.archetype.main \
+ -DarchetypeVersion=1.6.0-SNAPSHOT \
+ -DgroupId=<my.groupid> \
+ -DartifactId=<my-artifactId> \
+ -Dversion=<my.version> \
+ -DappName="<my app name>"
+
You change into the directory of the new project and then build the project by calling:
cd <my-artifactId>
+mvn clean package
+
+
Start the program using:
mvn exec:java
+
Happy coding =))
The following tools are used to develop Strolch and Strolch-based projects:
A Strolch agent’s architecture can be seen as a simple three-tier architecture. +The presentation layer is mostly a web frontend, where the communication with +the agent is done via REST API calls.
The agent itself implements the business logic using Services, Commands, +Queries etc.
The agent can communicate with other 3rd systems using any API, where it is +preferred to use JSON over REST.
The agent can use a standard RDBMS as a storage system, where currently DAOs +have been implemented only for PostgreSQL. Should it be required, then any +JDBC cabable RDBMS can be used, as no PostgreSQL specific SQL commands are used.
The following diagram helps visualize this:
The following diagram shows a more detailed view of the architecture of a +Strolch Agent:
A Strolch agent consists of the following main parts:
In addition to the main parts, Strolch contains inherit capabilities, which +gives Strolch unique features when compared to other Java Frameworks:
A Strolch agent can be easily extended with arbitrary components. An agent +is basically a container for classes extending StrolchComponent with a life +cycle. These classes mostly implement an interface which describes the +operations that are supported by the component.
The following represents a list of the most used components:
RealmHandler
: li.strolch.agent.impl.DefaultRealmHandlerPrivilegeHandler
: li.strolch.runtime.privilege.DefaultStrolchPrivilegeHandlerEnumHandler
: li.strolch.runtime.query.enums.DefaultEnumHandlerPolicyHandler
: li.strolch.policy.DefaultPolicyHandlerServiceHandler
: li.strolch.service.api.DefaultServiceHandlerStrolchSessionHandler
: li.strolch.rest.DefaultStrolchSessionHandlerPersistenceHandler
: multiple implementationsPostInitializer
: project specific implementationMailHandler
: li.strolch.handler.mail.SmtpMailHandlerA component has a life-cycle, which is governed by the Agent’s own life-cycle. +The life-cycle is as follows:
setup -> initialize -> start <-> stop -> destroy
+
The setup step is used to instantiate the component, the initialize step is +used to validate configuration parameters, and the run step is used to start +the component, i.e. start threads, etc. The stop step stops these threads and +also allows the component to be started again. The destroy step destroys the +instance and makes it unusable anymore, i.e. shutdown of the agent.
Each component has its own configuration parameters. A component is
+registered in the StrolchConfiguration.xml
file with a
The dependencies is an important feature as the dependencies of a component +are always started before the actual component.
By example of the MailHandler
we shall show how a strolch component would
+be implemented.
First define an interface:
public interface MailHandler {
+ void sendMail(String subject, String text, String recipient);
+}
+
Then implement a concrete MailHandler
:
public class SmtpMailHandler extends StrolchComponent implements MailHandler {
+
+ // instance fields with configuration properties to send the mail
+
+ public SmtpMailHandler(ComponentContainer container, String componentName) {
+ super(container, componentName);
+ }
+
+ @Override
+ public void initialize(ComponentConfiguration configuration) throws Exception {
+
+ // store any properties needed from the configuration
+
+ super.initialize(configuration);
+ }
+
+ @Override
+ public void sendMail(String subject, String text, String recipient) {
+
+ // send the e-mail using SMTP, or store in stack to send by thread
+ }
+}
+
Now that the component is written, it must be registered on the component, so
+that it is loaded when the agent is started. For this the
+StrolchConfiguration.xml
file must be modified to include a component
+element:
<StrolchConfiguration>
+ <env id="dev">
+ ...
+ <Component>
+ <name>MailHandler</name>
+ <api>li.strolch.handler.mail.MailHandler</api>
+ <impl>li.strolch.handler.mail.SmtpMailHandler</impl>
+ <Properties>
+ <username>test</username>
+ <password>test</password>
+ <hostName>localhost</hostName>
+ ...
+ </Properties>
+ </Component>
+ ...
+ </env>
+</StrolchConfiguration>
+
Now when the agent is started, the component can be retrieved and used. +E.g from inside a Service:
MailHandler mailHandler = getComponent(MailHandler.class);
+mailHandler.sendMail("My Subject", "Hello World", "test@test.ch");
+
The following is a simple list of do’s and don’ts:
Service
per use-case, should mostly delegate to Commands
.Commands
implement use-cases or parts of it, and are thus reusable.ResourceSearch
, OrderSearch
and ActivitySearch
when implementing use-case specific search - this allows privilege checking.tx.addCommand(Command)
and then tx.commitOnClose()
tx.flush()
if you need to perform first some work and then as late as possible call tx.commitOnClose()
ElementMaps
if really no other way, mostly use tx.get*By()
, tx.findBy()
and searches - if a specific get is missing, then add the method to StrolchTransaction
and send a pull request!tx.stream*()
methods to iterate over all elements, if you don’t want to use a search.StrolchElementToJsonVisitor
.ParameterBag
with the id relations
to the object and then StringParameters
with the value being the ID, the UOM set to the type of element being referenced and the Interpretation set to the class type being referenced.Strolch’s documentation has only just begun, but as more and more details of +the implementation in Strolch are fixed, more documentation can be created and +will be available here.
Currently we have the following topics of discussion:
Before we dive into the entire model, let’s show an example and how it would +be modelled in Strolch and use in Strolch:
A possible model would look as follows:
<?xml version="1.0" encoding="UTF-8" ?>
+<StrolchModel xmlns="https://strolch.li/xsd/StrolchModel-1.6.xsd">
+
+ <Resource Id="Product" Name="Product Template" Type="Template">
+ <ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
+ <Parameter Id="description" Name="Description" Type="String" Value=""/>
+ <Parameter Id="color" Name="Color" Type="String" Value=""/>
+ <Parameter Id="form" Name="Form" Type="String" Value=""/>
+ </ParameterBag>
+ <ParameterBag Id="relations" Name="Relations" Type="Relations">
+ <Parameter Id="articles" Name="Articles" Type="StringList" Interpretation="Resource-Ref" Uom="Article" Value=""/>
+ </ParameterBag>
+ </Resource>
+
+ <Resource Id="Article" Name="Article Template" Type="Template">
+ <ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
+ <Parameter Id="description" Name="Description" Type="String" Value=""/>
+ <Parameter Id="barcode" Name="Barcode" Type="String" Value=""/>
+ </ParameterBag>
+ <ParameterBag Id="relations" Name="Relations" Type="Relations">
+ <Parameter Id="product" Name="Product" Type="String" Interpretation="Resource-Ref" Uom="Product" Value=""/>
+ </ParameterBag>
+ </Resource>
+
+ <Resource Id="Customer" Name="Customer Template" Type="Template">
+ <ParameterBag Id="address" Name="Address" Type="Address">
+ <Parameter Id="street" Name="Street" Type="String" Value=""/>
+ <Parameter Id="zip" Name="Zip" Type="String" Value=""/>
+ <Parameter Id="city" Name="City" Type="String" Value=""/>
+ <Parameter Id="country" Name="Country" Type="String" Value=""/>
+ </ParameterBag>
+ </Resource>
+
+ <Order Id="Order" Name="Order" Type="Template">
+ <ParameterBag Id="quantities" Name="Quantities per Article Id" Type="Quantities">
+ <Parameter Id="quantity" Name="Quantity" Type="Float" Value="0"/>
+ </ParameterBag>
+ <ParameterBag Id="relations" Name="Relations" Type="Relations">
+ <Parameter Id="articles" Name="Articles" Type="StringList" Interpretation="Resource-Ref" Uom="Article" Value=""/>
+ <Parameter Id="customer" Name="Customer" Type="String" Interpretation="Resource-Ref" Uom="Customer" Value=""/>
+ </ParameterBag>
+ </Order>
+
+</StrolchModel>
+
Let’s go through this model:
Now that we have a basic understanding of te model, it is of far more interest +in how to create and interact with these elements at runtime. The following +listing will perform simple operations:
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate, false)) {
+
+ /*
+ * create a new product
+ */
+ Resource dafalgan = tx.getResourceTemplate("Product", true);
+ dafalgan.setName("Dafalgan 100mg");
+ dafalgan.getParameter("description", true).setValue("Dafalgan is for pain.");
+ dafalgan.getParameter("color", true).setValue("Yellow");
+ dafalgan.getParameter("form", true).setValue("flat");
+
+ StringListParameter articlesP = dafalgan.getRelationsParam("articles", true);
+
+ /*
+ * create articles
+ */
+ Resource dafalgan1 = tx.getResourceTemplate("Article", true);
+ dafalgan1.setName("Dafalgan 100mg 10 pce");
+ dafalgan1.getParameter("description", true).setValue("This is pack with 10 pieces.");
+ dafalgan1.getParameter("barcode", true).setValue("654654");
+
+ Resource dafalgan2 = tx.getResourceTemplate("Article", true);
+ dafalgan2.setName("Dafalgan 100mg 20 pce");
+ dafalgan2.getParameter("description", true).setValue("This is pack with 20 pieces.");
+ dafalgan2.getParameter("barcode", true).setValue("654655");
+
+ /*
+ * add reference to product
+ */
+ dafalgan1.getRelationParam("product").setValue(dafalgan.getId());
+ articlesP.addValue(dafalgan1.getId());
+ dafalgan2.getRelationParam("product").setValue(dafalgan.getId());
+ articlesP.addValue(dafalgan2.getId());
+
+ /*
+ * create a new customer
+ */
+ Resource customer1 = tx.getResourceTemplate("Customer", true);
+ customer1.setName("John Doe");
+
+ // set address
+ ParameterBag addressBag = customer1.getParameterBag("address", true);
+ addressBag.getParameter("street", true).setValue("Main Str. 1");
+ addressBag.getParameter("zip", true).setValue("1234");
+ addressBag.getParameter("city", true).setValue("Hometown");
+ addressBag.getParameter("country", true).setValue("Switzerland");
+
+ /*
+ * create a new order
+ */
+ Order order = tx.getOrderTemplate("Order", true);
+ order.setName("Order for " + customer1.getName());
+ order.setDate(LocalDate.of(2021, 2, 1));
+ order.setState(State.PLANNED);
+
+ // store reference to customer
+ order.getRelationParam("customer", true).setValue(customer1.getId());
+
+ StringListParameter orderArticlesP = order.getRelationsParam("articles", true);
+ ParameterBag quantitiesBag = order.getParameterBag("quantities", true);
+ FloatParameter quantityT = quantitiesBag.removeParameter("quantity");
+
+ // order quantity of 20 for Dafalgan 1
+ FloatParameter q1P = quantityT.getClone();
+ q1P.setId(dafalgan1.getId());
+ q1P.setValue(20);
+ quantitiesBag.addParameter(q1P);
+ orderArticlesP.addValue(dafalgan1.getId());
+
+ // set order quantity of 10 for Dafalgan 2
+ FloatParameter q2P = quantityT.getClone();
+ orderArticlesP.addValue(dafalgan2.getId());
+ q2P.setId(dafalgan2.getId());
+ q2P.setValue(20);
+ quantitiesBag.addParameter(q2P);
+
+ // keep IDs for later use
+ dafalganId = dafalgan.getId();
+ dafalgan1Id = dafalgan1.getId();
+ dafalgan2Id = dafalgan2.getId();
+ customerId = customer1.getId();
+ orderId = order.getId();
+
+ /*
+ * persist
+ */
+ tx.add(dafalgan);
+ tx.add(dafalgan1);
+ tx.add(dafalgan2);
+ tx.add(customer1);
+ tx.add(order);
+
+ // commit
+ tx.commitOnClose();
+}
+
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate, true)) {
+
+ // get order
+ Order order = tx.getOrderBy("Order", orderId, true);
+ assertNotNull(orderId);
+ assertEquals("Order for John Doe", order.getName());
+
+ // get customer
+ Resource customer = tx.getResourceByRelation(order, "customer", true);
+ assertNotNull(customer);
+ assertEquals("John Doe", customer.getName());
+
+ // get articles
+ List<Resource> articles = tx.getResourcesByRelation(order, "articles", true);
+ assertEquals(2, articles.size());
+
+ // get products
+ List<Resource> products = articles.stream().map(a -> tx.getResourceByRelation(a, "product", true))
+ .distinct().collect(Collectors.toList());
+ assertEquals(1, products.size());
+
+ // search for all orders in state PLANNED and with customer
+ List<Order> orders = new OrderSearch().types("Order").stateIsIn(State.PLANNED)
+ .where(ExpressionsSupport.relationParam("customer").isEqualTo(customerId)).search(tx).toList();
+ assertEquals(1, orders.size());
+}
+
Note: Checkout example-model.xml and SimpleModelTest.java for these examples.
There is an XML Schema which defines the model in XML: StrolchModel-1.6.xsd
Here is an example of all the possible elements in Strolch:
<StrolchModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+xmlns="https://strolch.li/xsd/StrolchModel-1.6.xsd"
+xsi:schemaLocation="https://strolch.li/xsd/StrolchModel-1.6.xsd StrolchModel-1.6.xsd">
+
+ <IncludeFile file="Include1.xml"/>
+
+ <Order Id="@test1" Name="Test Order" Type="Order">
+ <Version Version="0" CreatedBy="test" CreatedAt="2012-11-30T18:12:05.628+01:00" Deleted="false"/>
+ <ParameterBag Id="@bag01" Name="Test Bag" Type="TestBag">
+ <Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true"/>
+ </ParameterBag>
+ <ParameterBag Id="@bag01" Name="Test Bag" Type="TestBag">
+ <Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true"/>
+ </ParameterBag>
+ <Policies>
+ <Policy Type="PlanningPolicy" Value="key:SimplePlanning"/>
+ <Policy Type="ConfirmationPolicy" Value="key:NoConfirmation"/>
+ </Policies>
+ </Order>
+
+ <Resource Id="MyTestResource" Name="Test Name" Type="TestType">
+ <Version Version="0" CreatedBy="test" CreatedAt="2012-11-30T18:12:05.628+01:00" Deleted="false"/>
+ <ParameterBag Id="@bag01" Name="Test Bag 01" Type="TestBag">
+ <Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true"/>
+ </ParameterBag>
+ <ParameterBag Id="@bag02" Name="Test Bag 02" Type="TestBag">
+ <Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true"/>
+ </ParameterBag>
+ <TimedState Id="@booleanState" Name="Boolean State" Type="Boolean">
+ <Value Time="1970-01-01T00:02:00.000+01:00" Value="false"/>
+ </TimedState>
+ <Policies>
+ <Policy Type="PlanningPolicy" Value="key:SimplePlanning"/>
+ <Policy Type="ConfirmationPolicy" Value="key:NoConfirmation"/>
+ </Policies>
+ </Resource>
+
+ <Activity Id="activity_1" Name="Activity" Type="parentType" TimeOrdering="Series">
+ <Version Version="0" CreatedBy="test" CreatedAt="2012-11-30T18:12:05.628+01:00" Deleted="false"/>
+ <ParameterBag Id="@bag01" Name="Test Bag" Type="TestBag">
+ <Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true"/>
+ </ParameterBag>
+ <Policies>
+ <Policy Type="PlanningPolicy" Value="key:SimplePlanning"/>
+ <Policy Type="ConfirmationPolicy" Value="key:NoConfirmation"/>
+ </Policies>
+ <Action Id="action_1" Name="Action 1" ResourceId="dummyId" ResourceType="dummyType" State="Created" Type="Use">
+ <ParameterBag Id="@bag01" Name="Test Bag" Type="TestBag">
+ <Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true"/>
+ </ParameterBag>
+ <Policies>
+ <Policy Type="PlanningPolicy" Value="key:SimplePlanning"/>
+ <Policy Type="ConfirmationPolicy" Value="key:NoConfirmation"/>
+ </Policies>
+ <ValueChange StateId="dummyId" Time="2012-11-30T18:12:05.628+01:00" Value="5" Type="Integer"/>
+ <ValueChange StateId="dummyId" Time="2012-11-30T18:12:06.628+01:00" Value="6" Type="Integer"/>
+ </Action>
+ <Activity Id="child_activity" Name="Child Activity" Type="childType" TimeOrdering="Series">
+ <ParameterBag Id="@bag01" Name="Test Bag" Type="TestBag">
+ <Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true"/>
+ </ParameterBag>
+ <Policies>
+ <Policy Type="PlanningPolicy" Value="key:SimplePlanning"/>
+ <Policy Type="ConfirmationPolicy" Value="key:NoConfirmation"/>
+ </Policies>
+ <Action Id="action_2" Name="Action 2" ResourceId="dummyId" ResourceType="dummyType" State="Planned"
+ Type="Use">
+ <ValueChange StateId="dummyId" Time="2012-11-30T18:12:05.628+01:00" Value="5" Type="Integer"/>
+ <ValueChange StateId="dummyId" Time="2012-11-30T18:12:06.628+01:00" Value="6" Type="Integer"/>
+ </Action>
+ <Action Id="action_3" Name="Action 3" ResourceId="dummyId" ResourceType="dummyType" State="Created"
+ Type="Use"/>
+ </Activity>
+ </Activity>
+
+</StrolchModel>
+
All changes done in a Strolch transaction are recorded and then propagated to +any registered observers.
The observer feature is opt-in and is configured for each realm. In the
+StrolchConfiguration.xml
file enable observers by adding the
+enableObserverUpdates
property per realm:
<StrolchConfiguration>
+ <env id="dev">
+ ...
+ <Component>
+ <name>RealmHandler</name>
+ <api>li.strolch.agent.api.RealmHandler</api>
+ <impl>li.strolch.agent.impl.DefaultRealmHandler</impl>
+ <depends>PrivilegeHandler</depends>
+ <Properties>
+ <realms>defaultRealm, otherRealm</realms>
+ <enableObserverUpdates>true</enableObserverUpdates>
+ <dataStoreMode>TRANSIENT</dataStoreMode>
+ <dataStoreFile>StrolchModel.xml</dataStoreFile>
+ <enableObserverUpdates.otherRealm>true</enableObserverUpdates.otherRealm>
+ <dataStoreMode.otherRealm>TRANSIENT</dataStoreMode.otherRealm>
+ <dataStoreFile.otherRealm>StrolchModel.xml</dataStoreFile.otherRealm>
+ </Properties>
+ </Component>
+ </env>
+ ...
+</StrolchConfiguration>
+
Registering for updates is done by registering an Observer on the +ObserverHandler of the realm itself:
ObserverHandler observerHandler = container.getRealm(StrolchConstants.DEFAULT_REALM).getObserverHandler();
+observerHandler.registerObserver(Tags.RESOURCE, new Observer() {
+
+ @Override
+ public void update(String key, List<StrolchRootElement> elements) {
+ logger.info(elements.size() + " resources were updated!");
+ }
+
+ @Override
+ public void remove(String key, List<StrolchRootElement> elements) {
+ logger.info(elements.size() + " resources were removed!");
+ }
+
+ @Override
+ public void add(String key, List<StrolchRootElement> elements) {
+ logger.info(elements.size() + " resources were added!");
+ }
+});
+
Policies are an integral part when writing business logic in Strolch. In many
+cases it would suffice to write all such logic in Services
and Commands
, but
+as soon as behaviour can change, depending on the element being accessed, then
+this would quickly lead to many if/else blocks.
Since writing large if/else blocks is not maintanable in the long run, Strolch +offers a different approach. All Strolch elements can store Policy definitions. +This is a simple key/value store where the key defines the type of policy, and +the value references the policy to use.
Currently there are two ways to reference a policy in Strolch, either via a key
+which defines a further lookup in the PolicyHandler
, or directly as the name
+of the class to instantiate.
Using policies in Strolch gives the additional possibility of easily changing +the behaviour at runtime, as a Service and/or Command would delegate the +behaviour to the currently configured policy on the releveant element.
Policies are implemented by defining an abstract class and extends +StrolchPolicy. This abstract class then defines the API of the actual policy. A +concrete class then extends this abstract class and implements the concrete +methods.
Policies are registered on Resources, Orders, Activities and Actions. The +following shows defining two policies on a Resource, a PlanningPolicy, an +ExecutionPolicy in XML:
<Resource ...>
+ ...
+ <Policies>
+ <Policy Type="PlanningPolicy" Value="key:SimplePlanning" />
+ <Policy Type="ExecutionPolicy" Value="java:li.strolch.policytest.TestSimulatedExecutionPolicy" />
+ </Policies>
+</Resource>
+
Note how the PlanningPolicy has a value of key:SimplePlanning +and the ExecutionPolicy defines a reference to an actual class.
To use the PolicyHandler, it must be configured in the StrolchConfiguration.xml +as follows:
+<Component>
+ <name>PolicyHandler</name>
+ <api>li.strolch.policy.PolicyHandler</api>
+ <impl>li.strolch.policy.DefaultPolicyHandler</impl>
+ <Properties>
+ <readPolicyFile>true</readPolicyFile>
+ <policyConfigFile>StrolchPolicies.xml</policyConfigFile>
+ </Properties>
+</Component>
+
And this policy handler implementation requires a file where the lookups for the +policies is defined, e.g.:
+<StrolchPolicies>
+
+ <PolicyType Type="PlanningPolicy"
+ Api="li.strolch.policytest.TestPlanningPolicy">
+ <Policy Key="SimplePlanning"
+ Class="li.strolch.policytest.TestSimplePlanningPolicy"/>
+ </PolicyType>
+
+ <PolicyType Type="ConfirmationPolicy"
+ Api="li.strolch.policytest.TestConfirmationPolicy">
+ <Policy Key="NoConfirmation"
+ Class="li.strolch.policytest.TestNoConfirmationPolicy"/>
+ </PolicyType>
+
+</StrolchPolicies>
+
Now at runtime we can access the policies:
PolicyHandler policyHandler=getComponent(PolicyHandler.class);
+
+try(StrolchTransaction tx=openTx()){
+ Resource res=tx.getResourceBy("TestType","MyTestResource");
+ PolicyDefs policyDefs=res.getPolicyDefs();
+
+ PolicyDef planningPolicyDef=policyDefs.getPolicyDef("PlanningPolicy");
+ PlanningPolicy planningPolicy=policyHandler.getPolicy(planningPolicyDef,tx);
+ planningPolicy.plan(...);
+
+ PolicyDef executionPolicyDef=res.getPolicyDefs().getPolicyDef("ExecutionPolicy");
+ ExecutionPolicy executionPolicy=policyHandler.getPolicy(executionPolicyDef,tx);
+ executionPolicy.execute(...);
+}
+
No framework is complete without user management and privilege validation. The +basic form would be Users and Roles, and then validating that an authenticated +user has a given role. In Strolch we go a step further: A User has roles +assigned, and each role has a set of Privileges. The privileges can overlap, a +validation is performed to make sure that the one role doesn’t deny and another +role allows a specific action.
As explained in
+the Privilege Configuration section,
+users are defined in the PrivilegeUsers.xml
file, and roles are defined in the
+PrivilegeRoles.xml
file.
Let’s assume the following user and role definition:
<Users>
+ <User userId="1" username="jill" password="$PBKDF2WithHmacSHA512,10000,256$61646d696e$cb69962946617da006a2f95776d78b49e5ec7941d2bdb2d25cdb05f957f64344">
+ <Firstname>Jill</Firstname>
+ <Lastname>Someone</Lastname>
+ <State>ENABLED</State>
+ <Locale>en-GB</Locale>
+ <Roles>
+ <Role>AppUser</Role>
+ </Roles>
+ <Properties>
+ <Property name="realm" value="execution" />
+ </Properties>
+ </User>
+</Users>
+
<Roles>
+ <Role name="AppUser">
+ <Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege">
+ <AllAllowed>true</AllAllowed>
+ </Privilege>
+ <Privilege name="li.strolch.model.query.StrolchQuery" policy="DefaultPrivilege">
+ <AllAllowed>true</AllAllowed>
+ </Privilege>
+ <Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege">
+ <AllAllowed>true</AllAllowed>
+ </Privilege>
+ </Role>
+</Roles>
+
This configuration contains one user and one role. The user jill
has the role
+AppUser
and the role AppUser
has three privileges assigned.
Note how the user’s password is configured similar to a unix password +definition: Using the dollar sign & first the hashing algorithm is configured ( +algorithm, iterations, key length), then the salt, followed by the password +hash.
Note: The password can also still be saved using two separate fields: a salt and +password field. This configuration will be immediately changed to the unix form, +so won’t be described further here.
Further a user always has a firstname and last name. Optionally a locale can be
+set, otherwise the system locale is used. The user’s state must be defined as
+one of NEW
, ENABLED
, DISABLED
, EXPIRED
or SYSTEM
. A user can only
+authenticate/login with the state ENABLED
. A user can have any number of
+properties, which can then be used at runtime. A user can also reference any
+number of roles, the assigned privilege can overlap, a global configuration mode
+defines how duplicate privileges are handled.
Roles have a name and any number of Privilege
definitions. A Privilege has a
+name, which in many cases is the name of Java class/interface on which an action
+is being invoked. The policy
value defines which policy to use when evaluating
+the privilege access. The privilege definition is defined in the
+PrivilegeConfig.xml
and is the name of a class to call for privilege validation.
Further the privilege definitions can have a AllAllowed
boolean flag, or any
+number of Allow or Deny values. How these values are interpreted is defined in
+the policy implementation. A policy takes three input parameters:
PrivilegeContext
→ supplied by privilege and gives access to the Certificate,
+thus identifying the user for which privilege access is to be validated.IPrivilege
→ Contains the privilege values: AllAllowed
, Allow
and Deny
Restrictable
→ An interface from which the privilege name is retrieved, and
+the associated value. The value is an object, and is cast to the relevant
+input in the concrete privilege policy.The following privilege policies are already implemented:
DefaultPrivilege
→ simple policy where the passed Restrictable
is expected to
+return a String value, which is compared with allow and deny values.RoleAccessPrivilege
→ policy used for the internal privileges
+PrivilegeGetRole
, PrivilegeAddRole
, PrivilegeModifyRole
or PrivilegeModifyRole
UserAccessPrivilege
→ policy used for the internal privileges
+PrivilegeGetUser
, PrivilegeAddUser
, PrivilegeRemoveUser
, PrivilegeModifyUser
,
+PrivilegeAddRoleToUser
, PrivilegeRemoveRoleFromUser
, PrivilegeSetUserState
,
+PrivilegeSetUserLocale
or PrivilegeSetUserPassword
UserAccessWithSameOrganisationPrivilege
→ Same as the
+UserAccessPrivilege
but expects the authenticated user to have a property
+organisation
and validates that the user being modified is in the same
+organisation.UsernameFromCertificatePrivilege
→ This policy expects a
+Restrictable
to return the certificate
of another authenticated user and is
+used when modifying an authenticated user, i.e. killing a session, or
+modifying its current state, e.g. locale etc.UsernameFromCertificateWithSameOrganisationPrivilege
→ Same as
+UsernameFromCertificatePrivilege
but expects the authenticated user to have a
+property organisation
and validates that the user being modified is in the
+same organisation.Note: As a rule, the sequence is AllAllowed → Allow → Deny → default deny
The Query API is deprecated and the search API should be used instead.
As is custom for every framework, querying the model must be possible. Strolch
+queries are implemented using the StrolchQuery
interface and one of its concrete
+implementations: ResourceQuery
, OrderQuery
, ActivityQuery
.
A Strolch element always has two identifiers: Type
and Id
. The type is important
+as it classifies an element. So if a car and a house would be modelled in
+Strolch, then those would both be a Resource
, but one of type Car
and the other
+of type House
. Both would have different parameters.
Thus one of the inputs for every query is it’s type, which is defined as the +navigation. It is said that we navigate to the Cars, or Houses. Thus when +instantiating a ResourceQuery, pass the navigation to the type of Resource as +well. Same applies for Orders and Activities.
Further input for a StrolchQuery are the selections. These selections get
+translated into RDBMS WHERE
clauses. Selections support boolean operations thus
+allowing for complex querying.
StrolchQueries also support Ordering and object transformation. Following +classes provide the most used scenarios:
Example: Query all resources of type Car:
try (StrolchTransaction tx = openTx()) {
+ ResourceQuery<Resource> query = ResourceQuery.query("Car");
+ query.withAny();
+ List<Resource> cars = tx.doQuery(query);
+}
+
Example: Query all resources of type Car, order by Name and transform to JSON:
try (StrolchTransaction tx = openTx()) {
+ ResourceQuery<JsonObject> query = ResourceQuery.query("Car", new ResourceToJsonVisitor(),
+ new OrderByName());
+ query.withAny();
+ List<JsonObject> cars = tx.doQuery(query);
+}
+
the previous example can also be written as follows:
try (StrolchTransaction tx = openTx()) {
+ ResourceQuery<JsonObject> query = new ResourceQuery<>();
+ query.setNavigation(new StrolchTypeNavigation("Car"));
+ query.setResourceVisitor(new ResourceToJsonVisitor());
+ query.withAny();
+ List<JsonObject> cars = tx.doQuery(query);
+}
+
Example: Query all resources of type Car with color blue:
try (StrolchTransaction tx = openTx()) {
+ ResourceQuery<Resource> query = ResourceQuery.query("Car");
+ query.with(ParameterSelection.stringSelection("parameters", "color", "blue", StringMatchMode.es()));
+ List<Resource> cars = tx.doQuery(query);
+}
+
Example: Query all resources of type Car which are not blue:
try (StrolchTransaction tx = openTx()) {
+ ResourceQuery<Resource> query = ResourceQuery.query("Car");
+ query.not(ParameterSelection.stringSelection("parameters", "color", "blue", StringMatchMode.es()));
+ List<Resource> cars = tx.doQuery(query);
+}
+
Example: Query all resources of type Car with color blue or yellow:
try (StrolchTransaction tx = openTx()) {
+ ResourceQuery<Resource> query = ResourceQuery.query("Car");
+ query.or().with(
+ ParameterSelection.stringSelection("parameters", "color", "blue", StringMatchMode.es()),
+ ParameterSelection.stringSelection("parameters", "color", "yellow", StringMatchMode.es()));
+ List<Resource> cars = tx.doQuery(query);
+}
+
Example: Query all resources of type Car with color blue or yellow owned by Jill:
try (StrolchTransaction tx = openTx()) {
+ ResourceQuery<Resource> query = ResourceQuery.query("Car");
+
+ StringParameterSelection owner = ParameterSelection.stringSelection("parameters", "owner", "Jill", StringMatchMode.es());
+ OrSelection colors = new OrSelection().with(
+ ParameterSelection.stringSelection("parameters", "color", "blue", StringMatchMode.es()),
+ ParameterSelection.stringSelection("parameters", "color", "yellow", StringMatchMode.es()));
+
+ query.and().with(owner, colors);
+
+ List<Resource> cars = tx.doQuery(query);
+}
+
Realms implement multi-tenant capabilities. A Strolch agent can have an +arbitrary number of realms configured and each realm has its own persistence +configuration, allowing to separate mandates completely.
A realm can run in one of the following modes:
Realms are mostly hidden from a developer as a StrolchTransaction
exposes
+all important operations needed to access Strolch objects. A developer will
+however need to configure the realms for their specific project. If the
+project only requires one realm, then the defaultRealm
can be used, where the
+developer only is required to configure the mode and any relevant model file.
If the mode is CACHED
, then the PersistenceHandler
component is required to be
+configured, so that the DAOs know how to access the underlying database.
The configuration in the StrolchConfiguration.xml
file is as follows:
<StrolchConfiguration>
+ <env id="dev">
+ ...
+ <Component>
+ <name>RealmHandler</name>
+ <api>li.strolch.agent.api.RealmHandler</api>
+ <impl>li.strolch.agent.impl.DefaultRealmHandler</impl>
+ <depends>PrivilegeHandler</depends>
+ <!-- if CACHED: -->
+ <!--depends>PersistenceHandler</depends-->
+ <Properties>
+ <dataStoreMode>EMPTY|TRANSIENT|CACHED</dataStoreMode>
+ <dataStoreFile>StrolchModel.xml</dataStoreFile>
+ </Properties>
+ </Component>
+ ...
+ </env>
+</StrolchConfiguration>
+
A multi-realm configuration would be as follows.
Note how the defaultRealm is still enabled, and has its configuration as +before. Further the PostgreSQL PersistenceHandler is configured to show how the +realms are connected to the persistence handler:
<StrolchConfiguration>
+ <env id="dev">
+ ...
+ <Component>
+ <name>RealmHandler</name>
+ <api>li.strolch.agent.api.RealmHandler</api>
+ <impl>li.strolch.agent.impl.DefaultRealmHandler</impl>
+ <depends>PrivilegeHandler</depends>
+ <depends>PersistenceHandler</depends>
+ <Properties>
+ <realms>defaultRealm, cachedRealm</realms>
+ <dataStoreMode>TRANSIENT</dataStoreMode>
+ <dataStoreFile>DefaultRealm.xml</dataStoreFile>
+ <dataStoreMode.cachedRealm>CACHED</dataStoreMode.cachedRealm>
+ <dataStoreMode.emptyRealm>EMPTY</dataStoreMode.emptyRealm>
+ </Properties>
+ </Component>
+
+ <Component>
+ <name>PersistenceHandler</name>
+ <api>li.strolch.persistence.api.PersistenceHandler</api>
+ <impl>li.strolch.persistence.postgresql.PostgreSqlPersistenceHandler</impl>
+ <Properties>
+ <allowSchemaCreation>true</allowSchemaCreation>
+ <allowSchemaDrop>true</allowSchemaDrop>
+
+ <db.url.cachedRealm>jdbc:postgresql://localhost/testdb2</db.url.cachedRealm>
+ <db.username.cachedRealm>testuser2</db.username.cachedRealm>
+ <db.password.cachedRealm>test</db.password.cachedRealm>
+ <db.pool.maximumPoolSize.cachedRealm>1</db.pool.maximumPoolSize.cachedRealm>
+ </Properties>
+ </Component>
+ ...
+ </env>
+</StrolchConfiguration>
+
Accessing a realm is done in multiple ways. Important is to note, that a user
+should use the StrolchTransaction
object, instead of accessing the Realm directly.
Opening a transaction is done from a Service
by calling one of the
+openTx()
-methods. Nevertheless, the realm can be accessed as follows:
ComponentContainer container = getAgent().getContainer();
+StrolchRealm realm = container.getRealm(StrolchConstants.DEFAULT_REALM);
+try(StrolchTransaction tx = realm.openTx()) {
+ Resource resource = tx.getResourceBy("TestType", "MyTestResource");
+ ...
+}
+
Since Strolch has a generic model, it was rather straight forward to create a
+simple API for writing reports. In Strolch a report is defined by using its own
+model, i.e. a Report is a Resource
of type Report
.
A report consists of the following parts:
An example of a report is as follows:
+<Resource Id="stockReport" Name="Stock Report" Type="Report">
+
+ <ParameterBag Id="parameters" Name="parameters" Type="Parameters">
+ <Parameter Id="objectType" Index="20" Hidden="false" Name="Object Type"
+ Type="String" Interpretation="Resource-Ref" Uom="Player"
+ Value="Player"/>
+ <Parameter Id="descending" Name="Descending order" Type="Boolean"
+ Value="true"/>
+ </ParameterBag>
+
+ <ParameterBag Id="ordering" Name="Ordering" Type="Ordering">
+ <Parameter Id="name" Name="Name" Type="String"
+ Interpretation="Resource-Ref" Uom="Player" Value="$name"/>
+ </ParameterBag>
+
+ <ParameterBag Id="noTeamFilter" Name="Filter" Type="Filter">
+ <Parameter Id="policy" Name="Filter Policy" Type="String"
+ Interpretation="ReportFilterPolicy" Uom="key:Equals"
+ Value="!"/>
+ <Parameter Id="fieldRef" Name="Field reference" Type="String"
+ Interpretation="Resource-Ref" Uom="Slot"
+ Value="Bags/relations/team"/>
+ </ParameterBag>
+
+ <ParameterBag Id="columns" Name="Display Columns" Type="Display">
+ <Parameter Id="name" Name="Player" Type="String"
+ Interpretation="Resource-Ref" Uom="Player" Value="$name"/>
+ <Parameter Id="birthDate" Name="Birth date" Type="String"
+ Interpretation="Resource-Ref" Uom="Player"
+ Value="Bags/parameters/birthDate"/>
+ <Parameter Id="team" Name="Team" Type="String"
+ Interpretation="Resource-Ref" Uom="Team" Value="$name"/>
+ </ParameterBag>
+
+ <ParameterBag Id="joins" Name="Joins" Type="Joins">
+ <Parameter Id="Team" Index="10" Hidden="false" Name="Team" Type="String"
+ Interpretation="Resource-Ref" Uom="Team" Value="Player"/>
+ </ParameterBag>
+
+ <Policies>
+ <Policy Type="ReportPolicy"
+ Value="java:li.strolch.report.policy.GenericReport"/>
+ </Policies>
+
+</Resource>
+
This report
objectType
) → marks the object
+type to be show in the filter criteria (default), and that its sorting index
+is at 20.ordering
)noTeamFilter
)columns
)Team
GenericReport
class to generate the reportThe default generic report implemented in Strolch has the following features and +options:
The parameters bag can contain the following parameters:
objectType
→ the base type of object to get the input for the report. This
+means that the Interpretation
is set to one of:
Resource-Ref
Order-Ref
Activity-Ref
and that the UOM
and value
of the parameter is set to the type of element with
+which to retrieve the elements from the strolch model.
descending
→ boolean flag to define if sorting is in descending order
allowMissingColumns
→ flag to define if no exception should be thrown if a
+column is missing
dateRangeSel
→ defines a lookup parameter to use as a date range selector.
+This requires input when executing the report
Note: that the attributes Hidden and Index define the +visibility and sorting index as filter criteria respectively.
Many of the features of the generic report rely on looking up a value on the +referenced element. The following shows the ways that a lookup can be performed:
$id
→ lookup the ID of the element$name
→ lookup the name of the element$type
→ lookup the type of the element$date
→ lookup the date of the element (only possible on Order
+and Activity
elements)$state
→ lookup the state of the element (only possible on Order
+and Activity
elements)Bags/<bag_id>/<param_id>
→ a lookup on the selected element by bag ID and
+parameter ID$search:<parent_ref_id>:Bags/<bag_id>/<param_id>
→ searches for a parameter
+with the given bag and parameter, and if it does not exist, looks for the
+parent with the given parent_ref_id on the element. This allows a recursive
+search up a tree of elements which all have the same parameter referencing a
+parent. relations bagNote: these definitions are set as the value of a +Parameter, and the Interpretation and UOM of the parameter is used to find the +element on which to perform the lookup. I.e. the following definition:
<Parameter Id="name" Name="Player" Type="String" Interpretation="Resource-Ref" Uom="Player" Value="$name"/>
+
defines that we want to lookup the name of the resource of type Player.
Ordering, i.e sorting is done by adding the parameter bag with the id ordering
+and each parameter defines a column to order by. The sequence of the ordering is
+defined by the index
value assigned to each parameter.
Filtering use additional Strolch Policies which implement the operator function.
+I.e. performing an equals, etc. The following ReportFilterPolicy
are available
+and should be added in your StrolchPolicies.xml
file:
<StrolchPolicies>
+ ...
+ <PolicyType Type="ReportFilterPolicy" Api="li.strolch.report.policy.ReportFilterPolicy">
+ <Policy Key="GreaterThan" Class="li.strolch.report.policy.GreaterThanReportFilter"/>
+ <Policy Key="LessThan" Class="li.strolch.report.policy.LessThanReportFilter"/>
+ <Policy Key="Equals" Class="li.strolch.report.policy.EqualsReportFilter"/>
+ <Policy Key="Contains" Class="li.strolch.report.policy.ContainsReportFilter"/>
+ <Policy Key="IsIn" Class="li.strolch.report.policy.IsInReportFilter"/>
+ <Policy Key="ValueRef" Class="li.strolch.report.policy.ValueRefReportFilter"/>
+ </PolicyType>
+ ...
+</StrolchPolicies>
+
From this we can see that we can perform a GreaterThan
, LessThan
and Equals
+filtering. These filters can also be negated by prefixing the filter value with
+an exclamation mark (!).
A special case for the filter values are filters on dates. If you are filtering
+on a date, then you can use the special operator now()
. This filter will use the
+current date and time and will add/subtract the ISO8601 period passed as an
+argument to the operator.
The following shows examples of these filters:
<ParameterBag Id="minQtyFilter" Name="Filter" Type="Filter">
+ <Parameter Id="policy" Name="Filter Policy" Type="String" Interpretation="ReportFilterPolicy" Uom="key:GreaterThan" Value="10"/>
+ <Parameter Id="fieldRef" Name="Field reference" Type="String" Interpretation="Resource-Ref" Uom="Product" Value="Bags/parameters/quantity"/>
+</ParameterBag>
+
+<ParameterBag Id="notEmptyFilter" Name="Filter" Type="Filter">
+ <Parameter Id="policy" Name="Filter Policy" Type="String" Interpretation="ReportFilterPolicy" Uom="key:Equals" Value="!"/>
+ <Parameter Id="fieldRef" Name="Field reference" Type="String" Interpretation="Resource-Ref" Uom="Team" Value="Bags/relations/team"/>
+</ParameterBag>
+
+<ParameterBag Id="threeMonthsAgoFilter" Name="Filter" Type="Filter">
+ <Parameter Id="policy" Name="Filter Policy" Type="String" Interpretation="ReportFilterPolicy" Uom="key:LessThan" Value="now(-P3M)"/>
+ <Parameter Id="fieldRef" Name="Field reference" Type="String" Interpretation="Resource-Ref" Uom="FromStock" Value="$date"/>
+</ParameterBag>
+
Note: One parameter defines which policy gets used and the key:<name>
value
+references a policy defined in the StrolchPolicies.xml
file. Further the lookup
+is defined in the fieldRef
parameter.
To add columns from data which is not on the element denoted by the base object
+type, we can join further elements. This is done by adding the parameter bag
+joins
and then each parameter references an element to join. The joining is done
+as follows:
Intepretation
and UOM
define which object we want to join, i.e. resource
+of type fooInterpretation
and UOM
as the join definitionHidden
and Index
define the visibility and sorting index as
+filter criteria respectively.Thus the following:
<ParameterBag Id="joins" Name="Joins" Type="Joins">
+ <Parameter Id="Team" Index="10" Hidden="false" Name="Team" Type="String" Interpretation="Resource-Ref" Uom="Team" Value="Player"/>
+ <Parameter Id="Country" Index="5" Hidden="false" Name="Team" Type="String" Interpretation="Resource-Ref" Uom="Country" Value="Team"/>
+</ParameterBag>
+
Performs two joins: First we join a resource of type Team
by finding the
+relevant parameter on the Player
resource, and then we lookup a resource of type
+Country
on the previously joined Team
resource.
To execute a reports, we must instantiate the Report and can then directly +generate a JsonObject stream, which we can then pipe to a browser, file, etc.:
Stream<JsonObject> jsonObjectStream = new Report(tx, reportId).doReportAsJson();
+
If you prefer a CSV report:
try (CSVPrinter csvP = new CSVPrinter(new OutputStreamWriter(out),
+ CSVFormat.DEFAULT.withHeader(headers).withDelimiter(';'))) {
+
+ // do report without AsJson, and then iterating each row and sending to a CSV writer
+ report.doReport().forEach(row -> {
+ try {
+ csvP.printRecord(row.valueStream().collect(Collectors.toList())); // add to CSV
+ } catch (Exception e) {
+ logger.error("Could not write CSV row", e);
+ }
+ });
+}
+
Predefining filters is a good start, but in some case you only want a portion of +the actual filtered data. For instance if you make a stock report, you might +only want one location. This information is dynamic and thus not stored on the +report definition.
To perform these dynamic filterings, one would call the filter()
-method on the
+report, passing the type of element to be filtered, and to which element IDs to
+reduce the report data to. The following reduces the report to only return the
+rows with the product01
Product and location02
Location elements:
new Report(tx, "stockReport")
+ .filter("Product", "product01")
+ .filter("Location", "location02")
+ .doReportAsJson()
+
It is possible to find the possible filter criteria dynamically using the +generateFilterCriteria() method.
The last option to filter dynamically is using a date range selector. Define the +dateRangeSel lookup parameter, and then set the date range on the instantiated +report:
Model the report in XML:
<ParameterBag Id="parameters" Name="parameters" Type="Parameters">
+ ...
+ <Parameter Id="dateRangeSel" Name="Date Range Selector" Type="String" Interpretation="Resource-Ref" Uom="Product" Value="Bags/parameters/expirationDate"/>
+ ...
+</ParameterBag>
+
And now call the report in Java:
Date from = new Date(LocalDate.of(2016, 1, 1).toEpochDay() * 86400000);
+Date to = new Date(LocalDate.of(2017, 1, 1).toEpochDay() * 86400000);
+DateRange dateRange = new DateRange().from(from, true).to(to, false);
+List<JsonObject> result = new Report(tx, "stockReport") //
+ .filter("Product", "product01") //
+ .dateRange(dateRange) //
+ .doReportAsJson()
+
Note: See the GenericReportTest for examples.
A Strolch runtime configuration comprises two parts: a configuration part, and
+a model part. The configuration are files located in the ..config/
folder,
+and the model are files located in the ../data
folder.
In an absolute minimal configuration, the Strolch runtime requires the +following folder structure:
../config/
../StrolchConfiguration.xml
→ configures the Strolch agent../PrivilegeConfig.xml
→ configures user management../PrivilegeUsers.xml
→ contains the users in an XML based user management file../PrivilegeRoles.xml
→ contains the roles and privileges in an XML based user managementThe StrolchConfiguration.xml file configures the Strolch agent. The StrolchConfiguration.xml defines the following:
<StrolchConfiguration>
root element<env id="xxx">
different environments with the possibility of having a
+global environment for configuration valid in multiple environments.<Runtime>
element which defines the agents name and a few other
+properties e.g. locale
and verbose
:<applicationName>
the agent’s name<Properties>
<locale>
the agent’s internal locale for log messages etc.<verbose>
the logging level for some internal logging. (Logging is
+mostly done using log4j over slf4j)<Component>
elements for each component used in the agent. A component
+is configured by defining the following child elements:<name>
the name of the component, use when defining dependencies
+between components. The name is mostly set to the simple name of the
+interface of the component
<api>
the full class name to the interface of the component. During
+runtime this interface will be used to access the component e.g.:
ServiceHandler svcHandler = agent.getContainer().getComponent(ServiceHandler.class);
<impl>
the full class name of the concrete implementation of the
+component. During initialization this class will be instantiated and
+registered under the component name and interface. This class must
+extend the class li.strolch.agent.api.StrolchComponent
<depends>
any number of these elements, where the content is the name
+of another component, on which this component depends. Depending
+components are initialized and started after the component they depend
+on and are stopped and destroyed before
<Properties>
<...>
any number of properties which the component requires. The
+element’s name will be the key with which the value can be accessed at
+runtime.
When a property is missing, and the component has a hard coded default value, then when the component is initialized, the use of the default value and its key is logged. This makes it easy to see which new properties can be configured. Should the component not define a default value, then the component will thrown an exception on initialization. In this case it can be a good moment to read the JavaDoc (or source code) for the component in question to see how it is configured.
In Strolch authentication and authorization is baked in. To open a transaction, +and thus access the Strolch model, a Certificate object is required, which +means the user has been authenticated and possibly authorized.
The PrivilegeConfig.xml defines the following:
<Privilege>
root element<Container>
configures the individual Privilege components<Parameters>
base configuration properties for Privilege<EncryptionHandler>
configures the hashing algorithms and other
+encryption specific configuration<PersistenceHandler>
configures the persistence of the roles and users<UserChallengeHandler>
configures a challenge handler so that a user
+can reset their password. The default challenge handler is the
+li.strolch.privilege.handler.MailUserChallengeHandler
which sends a
+challenge to the user’s defined e-mail address.<SsoHandler>
the SSO Handler is used to implement a SingleSignOn and
+can be used to start a session using a LDAP token, etc. There is no
+default implementation as this is project specific.<Policies>
configures the available privilege policies at runtime, the
+name is referenced from the model fileThe PrivilegeUsers.xml
and PrivilegeRoles.xml
define the users and roles
+and is used when in PrvilegeConfig.xml
the PersistenceHandler
is set to
+ch.eitchnet.privilege.handler.XmlPersistenceHandler
:
<Users>
configures all users<User>
configures a specific user<Firstname>
configures a user’s first name<Lastname>
configure a user’s last name<State>
configures the user’s state, see li.strolch.privilege.model.UserState
<Locale>
configure the user’s locale<Roles>
configures the user’s roles<Role>
adds a role to the user<Properties>
configures user specific properties. What properties
+are used is not specified and is dependent on the concrete agent<Property>
defines a single property<Roles>
configures all roles<Role>
configures a specific role<Privilege>
configures a specific privilege for this role<AllAllowed>
if set to true, then defines that all values
+associated with this privilege are allowed<Allow>
defines one allowed value for this privilege<Deny>
defines one denied value for this privilegeImplementing a strolch component requires an interface, which defines the
+component’s API and a concrete class which implements the interface and
+extends the class StrolchComponent
.
The StrolchComponent class adds the state model to the class, which +transitions as follows:
UNDEFINED => SETUP => INITIALIZED => STARTED <=> STOPPED => DESTROYED
Components can switch between STARTED
and STOPPED
, but once DESTROYED
no
+further state change is possible. The component’s state is changed by changes
+to the agent’s lifecycle.
A component’s state is changed by a call to the appropriate method on the
+component, override the methods as necessary. Note that it is good practice
+that the initialize()
-method is used to get all the configuration properties,
+and that they should there be evaluated and that the method so return quickly.
+The start()
-method is called after the agent’s initialization and should be
+where additional threads are started. Correctly implementing these methods
+allows to quickly detect a wrongly configured agent, which might take longer
+to start for whatever reason.
The following shows a basic implementation of a component on the basis of a
+post initializer (a component which performs some actions in its
+start()
-method which should be done after everything else is started in the
+agent).
public class SimplePostInitializer
+ extends StrolchComponent
+ implements PostInitializer {
+
+ public SimplePostInitializer(ComponentContainer container,
+ String componentName) {
+ super(container, componentName);
+ }
+
+ @Override
+ public void initialize(ComponentConfiguration configuration) {
+ // do some initialization, validate configuration values, etc.
+ // now call super, to update state
+ super.initialize(configuration);
+ }
+
+ @Override
+ public void start() {
+ // start any threads, or perform long running start work
+ // now call super, to update state
+ super.start();
+ }
+
+ @Override
+ public void stop() {
+ // stop threads and timers, but be ready to start again
+ // now call super, to update state
+ super.stop();
+ }
+
+ @Override
+ public void destroy() {
+ // destroy this component, release all resources and don't worry about
+ // being called to start again now call super, to update state
+ super.destroy();
+ }
+}
+
The new component would then be registered in the StrolchConfiguration.xml
+as follows:
<StrolchConfiguration>
+ <env id="...">
+ ...
+ <Component>
+ <name>SimplePostInitializer</name>
+ <api>li.strolch.agent.api.PostInitializer</api>
+ <impl>li.strolch.documentation.SimplePostInitializer</impl>
+ </Component>
+ ...
+ </env>
+</StrolchConfiguration>
+
And can be access at runtime using:
PostInitializer postInitializer = getContainer().getComponent(PostInitializer.class);
+
When a Strolch runtime is started, then the root path to the runtime configuration must be passed. In Java this is done by calling:
StrolchAgent agent = new StrolchAgent();
+agent.setup(environment, rootPath);
+agent.initialize();
+agent.start();
+
In Servlet 3.0 applications one would implement the
+javax.servlet.ServletContextListener
interface, add the @WebListener
+annotation to the class and in the contextInitialized()
-method start Strolch:
String realPath = sce.getServletContext().getRealPath("/WEB-INF");
+String environment = StrolchEnvironment.getEnvironmentFromEnvProperties(pathF);
+this.agent = new StrolchAgent();
+this.agent.setup(environment, new File(realPath));
+this.agent.initialize();
+this.agent.start();
+
As is custom for every framework, querying, or searching, the model must be
+possible. Strolch searches are implemented using the StrolchSearch
class and
+one of its concrete implementations: ResourceSearch
, OrderSearch
,
+ActivitySearch
.
A Strolch element always has two identifiers: Type
and Id
. The type is
+important as it classifies an element. So if a car and a house would be modelled
+in Strolch, then those would both be a Resource
, but one of type Car
+and the other of type House
. Both would have different parameters. Thus when
+searching for objects, the first thing to do is define the type of object being
+searched.
The Strolch search API is very expressive and offers multiple ways to perform +the same search. The search API consists of three components: The search +classes, the search expressions and the search predicates. The concept was taken +from the Apache Camel project.
There are four main search classes:
No search is useful without a where
clause, which are called search
+expressions. When writing a search, there are multiple ways to add such where
+clauses. Either
define()
-method in your sub class and add the where clauses by
+calling the where()
method, orbyColor()
which also calls the
+where()
-method to add a search expression, orwhere()
-method after instantiating a search.When extending the class, then the search expressions are available as methods +on the super class, otherwise you can statically import them from +ExpressionsSupport +.
And of course a where clause needs operators, which are called search +predicates. Just as search expressions are available in sub classes, so are +search predicates and can also be statically imported through +PredicatesSupport +.
Examples of search expressions with search predicates follow:
ResourceSearch search=new ResourceSearch();
+
+// predicate either as parameter, or chained
+search.where(id().isEqualTo("myId"));
+search.where(id(isEqualTo("myId")));
+
+// negating
+search.where(id(isEqualTo("myId")).not());
+
+search.where(param("bagId","paramId").isIn(Arrays.asList("red","blue","green")));
+
+search.where(paramNull("bagId","paramId")));
+
+// boolean operations
+search.where(id(isEqualTo("myId")) //
+ .or(name(isEqualTo("myName"))));
+
Note how the predicates can be chained to the search expression, or passed as a +parameter to the expression.
In addition to using predefined search search expressions, one can also just +pass a lambda expression which performs a custom filter:
personSearch.where(person -> person.getName().length() == 3);
+
See +the StrolchSearchTest +for many ways in which you can implement tests.
Note that strolch searches requires privileges, thus when you
+use a strolch search, add it to the role of the user in PrivilegeRoles.xml
:
+<Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege">
+ <Allow>internal
+ </Allow> <!-- internal used for when the search is done in an internal service -->
+ <Allow>li.strolch.bookshop.search.BookSearch</Allow>
+</Privilege>
+
Services
are written to implement a specific use-case. Commands
are written to
+implement re-usable parts of a use-case. The use-case can be abstract
+e.g., AddResourceService
or very specific e.g. CreatePatientService
.
Should the use-case be re-usable in different scenarios, then commands should
+implement the logic, and the services should then execute the commands. E.g.
+The CreatePatientService
would use a CreatePatientResourceCommand
and then
+use an AddResourceCommand
in a single transaction, so that the task of
+creating the actual Patient Resource can be re-used somewhere else.
Services extend the abstract class AbstractService
and then implement the
+method internalDoService(ServiceArgument)
. AbstractService defines generic
+template arguments with which the concrete service can define a specific
+input ServiceArgument class and output ServiceResult class.
The AbstractService class has multiple helper methods:
openTx():StrolchTransaction
- to open a transactionrunPrivileged()
- to perform a SystemUserAction
getComponent():V
- to retrieve a specific StrolchComponentthere are more - check the JavaDocs.
Commands extend the Command
class and then implement the method doCommand()
.
+Commands have helper methods:
tx()
- to get the current transactiongetPolicy()
- to retrieve a StrolchPolicy
instancerunPrivileged()
- to perform a SystemUserAction
there are more - check the JavaDocs.
The following code snippets shows how a Service and Command are used to +perform the task of adding a new Order. Note how:
tx.commitOnClose()
AddOrderService:
public class AddOrderService extends AbstractService<AddOrderService.AddOrderArg, ServiceResult> {
+
+ @Override
+ protected ServiceResult getResultInstance() {
+ return new ServiceResult();
+ }
+
+ @Override
+ protected ServiceResult internalDoService(AddOrderArg arg) {
+
+ try (StrolchTransaction tx = openTx(arg.realm)) {
+ AddOrderCommand command = new AddOrderCommand(getContainer(), tx);
+ command.setOrder(arg.order);
+ tx.addCommand(command);
+ tx.commitOnClose();
+ }
+
+ return ServiceResult.success();
+ }
+
+ public static class AddOrderArg extends ServiceArgument {
+ public Order order;
+ }
+}
+
AddOrderCommand:
public class AddOrderCommand extends Command {
+
+ private Order order;
+
+ public AddOrderCommand(ComponentContainer container, StrolchTransaction tx) {
+ super(container, tx);
+ }
+
+ public void setOrder(Order order) {
+ this.order = order;
+ }
+
+ @Override
+ public void validate() {
+ DBC.PRE.assertNotNull("Order may not be null!", this.order);
+ }
+
+ @Override
+ public void doCommand() {
+
+ tx().lock(this.order);
+
+ OrderMap orderMap = tx().getOrderMap();
+ if (orderMap.hasElement(tx(), this.order.getType(), this.order.getId())) {
+ String msg = MessageFormat.format("The Order {0} already exists!", this.order.getLocator());
+ throw new StrolchException(msg);
+ }
+
+ orderMap.add(tx(), this.order);
+ }
+
+ @Override
+ public void undo() {
+ if (this.order != null && tx().isRollingBack()) {
+ OrderMap orderMap = tx().getOrderMap();
+ if (orderMap.hasElement(tx(), this.order.getType(), this.order.getId()))
+ orderMap.remove(tx(), this.order);
+ }
+ }
+}
+
Strolch Transactions play a central role in a Strolch agent. A transaction is
+opened for a realm, and grants access to the model of the agent. Transactions
+are implemented as a Java try-with-resources
by implementing
+the AutoCloseable
+interface. This makes it trivial to understand the scope of a transaction.
Transactions handle the following:
tx.lock(StrolchRootElement)
or
+tx.lock(Locator)
was calledWhen a transaction is opened, it is by default read-only, i.e. does not perform
+any commands when it is closed. Should the TX perform commands, then it is
+important to call tx.commitOnClose()
, but only at the end of the work, so that
+exception handling can properly work if something goes wrong.
StrolchTransaction
offers a myriad of methods:
Locator
StringParameter
or
+StringListParameter
referencesTransactions are opened by accessing the realm, but there are convenience +methods depending on the use-case:
openTx()
-methodstx()
to get instance.RestfulStrolchComponent.openTx()
Note: don’t open a new TX inside a TX for the same realm!
Important is to always open the transaction as a try-with-resource
block and to
+define if the TX should commit, or not:
try (StrolchTransaction tx = openTx(...)) {
+
+ // read lock our object
+ Locator ferrariLoc = Resource.locatorFor("Car", "ferrari");
+ tx.lock(ferrariLoc);
+
+ // find a car by locator
+ Resource ferrari = tx.findElement(ferrariLoc);
+
+ // get a car by ID
+ Resource opel = tx.getResourceBy("Car", "opel", true);
+
+ // modify ball
+ opel.setName("Opel Corsa");
+ tx.update(opel);
+
+ // get by string reference
+ StringParameter ownerP = ferrari.getParameter("relations", "owner", true);
+ Resource owner = tx.getResourceBy(ownerP, true);
+
+ // get by string list reference
+ StringListParameter previousOwnersP = opel.getParameter("relations", "previousOwners", true);
+ List<Resource> previousOwners = tx.getResourcesBy(previousOwnersP, true);
+
+ // check resource exists
+ if (tx.hasResource("Car", "audi")) {
+ Resource audi = tx.getResourceBy("Car", "audi", true);
+
+ // assert has privilege to remove a car
+ tx.assertHasPrivilege(Operation.REMOVE, audi);
+
+ // remove the car
+ tx.remove(audi);
+ }
+
+ // iterate all cars
+ tx.streamResources("Car").forEach(car -> {
+ logger.info("Car: " + car.getId());
+ });
+
+ // commit if TX was changed
+ if (tx.needsCommit())
+ tx.commitOnClose();
+}
+
One of Strolch’s features that sets it apart from other frameworks, is that +versioning is baked into Strolch’s fabric. The feature is opt-in, as it is not +required in all projects, but it only needs enabling, for all modifications to +objects to be versioned, so that rollbacks can be done when needed.
The feature is enabled for each realm. In the StrolchConfiguration.xml
file
+enable it by adding the enableVersioning
propery per realm:
<StrolchConfiguration>
+ <env id="dev">
+ ...
+ <Component>
+ <name>RealmHandler</name>
+ <api>li.strolch.agent.api.RealmHandler</api>
+ <impl>li.strolch.agent.impl.DefaultRealmHandler</impl>
+ <depends>PrivilegeHandler</depends>
+ <Properties>
+ <realms>defaultRealm, otherRealm</realms>
+ <enableVersioning>true</enableVersioning>
+ <dataStoreMode>TRANSIENT</dataStoreMode>
+ <dataStoreFile>StrolchModel.xml</dataStoreFile>
+ <enableVersioning.otherRealm>true</enableVersioning.otherRealm>
+ <dataStoreMode.otherRealm>TRANSIENT</dataStoreMode.otherRealm>
+ <dataStoreFile.otherRealm>StrolchModel.xml</dataStoreFile.otherRealm>
+ </Properties>
+ </Component>
+ </env>
+ ...
+</StrolchConfiguration>
+
Once versioning is enabled, versioning is handled automatically. The API for versioning is implemented on the ElementMaps.
Example: Revert to previous version of a Resource:
Resource res = tx.getResourceBy("TestType", "MyTestResource");
+ResourceMap resourceMap = tx.getResourceMap();
+Resource previousVersion = resourceMap.revertToVersion(tx, res);
+// or
+Resource previousVersion = resourceMap.revertToVersion(tx, "TestType", "MyTestResource", 1);
+
Example: Retrieve all versions of a Resource:
List<Resource> versions = resourceMap.getVersionsFor(tx, "TestType", "MyTestResource");
+
Note: When reverting to a previous version, it is important to remember, that +any references on an element to other elements will also be restored. As long as +the relationship is to the same element, then this is not an issue, but should +the relationship have changed, then it this must be handled and the user +performing a revert be allowed to decided which element to reference in the +reverted version.
Strolch is +on Maven central +, but if the latest version is not there, then build it locally. A guide can be +found on the development page.
Strolch is also built on Jenkins, so you can see if the +latest version passes all tests.
Strolch is an open source component based software agent written in Java and can be compared, in a light sense, with the Java EE stack: Strolch takes care of persistence, implements Services for use cases, Commands as re-usable algorithms and has a parameterized data model.
Strolch has an intrinsic understanding for mandates, which are called realms so that a single agent can be used to implement applications with multiple users/customers for instance in SaaS environments.
The parameterized data model consists of three top level objects, Resources, Orders and Activities. These objects can have any number of ParameterBags which in turn can have any number of Parameters on them. This allows for a very dynamic modelling of data structures including modification at run time. Multiple ready to use Parameter types are already implemented which handle the primitive types in Java including ListParameters for collections of these primitive types.
One of the main features of the Strolch agent, is that persistence is handled transparently and the user must not be worried about databases and the likes. Currently there are two implementations for persisting the Strolch model, a PostgreSQL and an XML file persistence. Currently both persistence layers persist the data by converting to XML and storing it into the database. The XML file persistence stores each object in its own file.
The agent itself has a small memory footprint and requires very few components to start. For the agent to be useful it needs additional functionality which is implemented in StrolchComponents. Each component is registered via its Java interface on the agent and is bound to the life cycle of the agent. When the agent is started, these components can be retrieved and used to perform any number of functionalities. This is the preferred way to extend the Strolch agent. There are a number of components already implemented, e.g. the ServiceHandler which executes Services in a controlled fashion and can validate authorized access to these services.
No software product is complete without a system for authentication and authorization. Strolch implements this by using the Privilege framework which has been written by Robert von Burg. The standard ServiceHandler detects the existence of the PrivilegeHandler and then validates that the user has authorization to perform the service. This framework is implemented as its own Strolch component, thus can be retrieved at any time during execution to perform fine grained and special authorization validation.
A question often asked is why create Strolch. What are its benefits in contrast to using Java SE with an OR-Mapper like Hibernate, or using Java EE on JBoss or Glassfish? Especially since many of the features existing in those stacks needed to be re-created in Strolch.
The first answer to this question is that those systems are often overly complicated and bloated. Java SE with Hibernate certainly is a viable option when it comes to being light-weightier but Hibernate, even though it is supposed to, often fails to truly help remove the need to really understand an RDBMS. Often enough Hibernate will just get in the way of the most important part of development: writing the business code. Being an OR-Mapper which is supposed to implement all the nitty-gritty details of an RDBMS system, Hibernate, and JPA for that matter, still often has the developer go back to understanding these details.
Strolch tries a different approach to persistence. Instead of writing pojos/entities, Strolch’s model has the concept that each element’s attributes are part of a composition pattern: each attribute is its own object and thus can be dynamically changed at runtime, but also makes persistence of such an element generic. Instead of having fixed attributes for a concrete class, these parameters are stored in a map and are accessed through the parameter’s ID.
Assigning an ID to an attribute for accessing of course brings its own downsides, i.e. the parameter might simply not be there, when being accessed. This is certainly an issue that the developer must handle, when implementing a project using Strolch, but allows the developer to not need to worry about persistence, as this is generically handled.
Since the persistence is generically handled, and Strolch stays lightweight on its requirements at runtime, the developer can quickly get down to what is important for business value: Writing the business logic and the presentation layer. Here too Strolch tries to help the developer by bringing in concepts which are easy to follow: Use cases are implemented as Services, and re-usable business logic is put into Commands.
There will be reasons against using Strolch, as there will be against using the Java EE stack, or an OR-Mapper or even the Java ecosystem for that fact. Important is to note, that the concepts behind Strolch are nothing new, but have been implemented in at least two previous proprietary products. Since those products are not accessible to the public, it was decided that a re-implementation might be of use to the programming community at large.
Currently, there is at least one company using Strolch in a commercial project which helps drive Strolch’s development and further motivates its existence.
Strolch is an open source project and licensed under the Apache License 2.0.
##Technology +Strolch is written in Java and is programmed against the JDK 8. Strolch runs on any JRE 8 compliant environment. Strolch is tested on the Oracle JRE 8.
Strolch is framework for developing Software. It’s main features are:
It is a different framework to Spring and other similar type of Java +frameworks, as the model is defined as an abstract model, where you +always have the same three types of objects: Resources, Orders and +Activities. The fields are mapped as Parameter objects, of which the +important primitives are available.
The nice part about this framework is, that you can be up and ready in +a matter of minutes, and start building your project immediately in +that you open your favourite XML editor and start modelling your data.
Once your data is defined, you write your business logic in the form +of Services, Commands and Searches. There are many predefined services +and commands to manipulate the object model, so that you write your own +services when you need to enforce special business rules.
Through the use of Policy objects, you decouple algorithms from your +object model, so that at runtime you can change the behaviour, or +easily implement different behaviour depending on your use-case. For +instance you might have a simple billing service which performs a few +preparatory steps, and then calls the configured billing policy to +execute the billing depending on the customer, the warehouse, etc.
And of course persistence is as simple as configuring the persistence +handler, pointing to your RDBMS and then setting the mode to CACHED. +For you as a developer there is no more thinking in terms of SQL etc., +as this is completely hidden from the developer. There is even a simple +file persistence layer if you are running IoT devices.
The runtime can be just about anything. Usually it is run inside an +Apache Tomcat instance as a webapp, as a WEB UI has been required for +all current Strolch projects. You could just as well use a main class. +Accessing the Strolch Agent remotely is usually done through REST.
Strolch is being actively developed, and customers constantly give us +reasons to improve and extend the framework. There is a Polymer +Inspector component which makes it easy to see and manipulate the +actual data. The new Search API makes it really easy to query your data.
Yes, Strolch is different, but the concept has come out of the planning +and execution segment, and has been refined over the years until it has +become what it is today.
Check out the API page to see how to use Strolch.
The Strolch PLC architecture sees the Strolch Agent as the server, managing +logical devices, i.e. multiple sensors and actors together and thus deciding on +further steps. With this architecture multiple PLCs can be combined together in +one agent for flow control.
On the agent side the two main classes are the PlcGwServerHandler
and the
+PlcGwService
The PlcGwServerHandler
handles connections from remote PLCs over WebSockets and
+sends the requests to these PLCs. A PlcGwService
instance will be notified and
+can then decide on an action. In an execution model with Activities, the
+PlcNotificationListener
interface can be implemented, or the PlcExecutionPolicy
+can be directly extended.
On the PLC side, the PlcGwClientHandler
is optional if no agent is required. The
+PlcHandler
initializes the model and connections. The Plc
class is Strolch
+agnostic and manages the connections and notifies PlcListener
instances on
+changes coming from the underlying connections. The PlcService
implementations
+implement business logic, and can also be notified on updates from connections.