diff --git a/li.strolch.agent/src/main/java/li/strolch/service/api/Command.java b/li.strolch.agent/src/main/java/li/strolch/service/api/Command.java index 5ddbcec1b..1cbae93d6 100644 --- a/li.strolch.agent/src/main/java/li/strolch/service/api/Command.java +++ b/li.strolch.agent/src/main/java/li/strolch/service/api/Command.java @@ -29,6 +29,7 @@ import li.strolch.policy.StrolchPolicy; import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.handler.SystemUserAction; import li.strolch.privilege.model.Restrictable; +import li.strolch.runtime.StrolchConstants; /** *

@@ -121,6 +122,21 @@ public abstract class Command implements Restrictable { return this.container.getPrivilegeHandler().runAsSystem(username, action); } + /** + * Performs the given {@link SystemUserAction} as the privileged system user + * {@link StrolchConstants#PRIVILEGED_SYSTEM_USER}. Returns the action for chaining calls + * + * @param action + * the action to perform + * + * @return the action performed for chaining calls + * + * @throws PrivilegeException + */ + protected V runPrivileged(V action) throws PrivilegeException { + return this.container.getPrivilegeHandler().runAsSystem(StrolchConstants.PRIVILEGED_SYSTEM_USER, action); + } + /** * Returns the {@link StrolchTransaction} bound to this {@link Command}'s runtime * diff --git a/li.strolch.website/www.strolch.li/documentation.html b/li.strolch.website/www.strolch.li/documentation.html index f022c5742..a358eebb8 100644 --- a/li.strolch.website/www.strolch.li/documentation.html +++ b/li.strolch.website/www.strolch.li/documentation.html @@ -55,14 +55,19 @@

Currently we have the following topics of discussion:

- -

Not yet written, but soon to come are:

- diff --git a/li.strolch.website/www.strolch.li/documentation_business_logic.html b/li.strolch.website/www.strolch.li/documentation_business_logic.html deleted file mode 100644 index 6f94fa638..000000000 --- a/li.strolch.website/www.strolch.li/documentation_business_logic.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - Strolch: Business Logic - - - - - - - - - - - - -
- - - -
-

Writing business logic in Strolch

- - - -
- - - - -
- - - - - - - - - - - - - - diff --git a/li.strolch.website/www.strolch.li/documentation_components.html b/li.strolch.website/www.strolch.li/documentation_components.html new file mode 100644 index 000000000..12798eecd --- /dev/null +++ b/li.strolch.website/www.strolch.li/documentation_components.html @@ -0,0 +1,208 @@ + + + + + + + + + + + + Strolch: Components + + + + + + + + + + + + +
+ + + +
+ +

A Strolch agent can be easily extended with arbitrary components. An agent is basically a container for + classes extending StrolchComponent. Theses 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:

+ + +

A 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 {
+      public 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");
+        
+ + + + + +
+ + + + +
+ + + + + + + + + + + + + + diff --git a/li.strolch.website/www.strolch.li/documentation_queries.html b/li.strolch.website/www.strolch.li/documentation_queries.html new file mode 100644 index 000000000..094439601 --- /dev/null +++ b/li.strolch.website/www.strolch.li/documentation_queries.html @@ -0,0 +1,201 @@ + + + + + + + + + + + + Strolch: Queries + + + + + + + + + + + + +
+ + + +
+ +

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);
+}
+ + + +
+ + + + +
+ + + + + + + + + + + + + + diff --git a/li.strolch.website/www.strolch.li/documentation_realms.html b/li.strolch.website/www.strolch.li/documentation_realms.html new file mode 100644 index 000000000..c8d411339 --- /dev/null +++ b/li.strolch.website/www.strolch.li/documentation_realms.html @@ -0,0 +1,221 @@ + + + + + + + + + + + + Strolch: Realms + + + + + + + + + + + + +
+ + + +
+ +

Realms implement multi-mandate 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 or TRANSACTIONAL, 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 or TRANSACTIONAL: -->
+      <!--depends>PersistenceHandler</depends-->
+      <Properties>
+        <dataStoreMode>EMPTY|TRANSIENT|CACHED|TRANSACTIONAL</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, transactionalRealm</realms>
+        <dataStoreMode>TRANSIENT</dataStoreMode>
+        <dataStoreFile>DefaultRealm.xml</dataStoreFile>
+        <dataStoreMode.transactionalRealm>TRANSACTIONAL</dataStoreMode.transactionalRealm>
+        <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.transactionalRealm>jdbc:postgresql://localhost/testdb1</db.url.transactionalRealm>
+        <db.username.transactionalRealm>testuser1</db.username.transactionalRealm>
+        <db.password.transactionalRealm>test</db.password.transactionalRealm>
+        <db.pool.maximumPoolSize.transactionalRealm>1</db.pool.maximumPoolSize.transactionalRealm>
+
+        <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");
+  ...
+}
+
+ + + + +
+ + + + +
+ + + + + + + + + + + + + + diff --git a/li.strolch.website/www.strolch.li/documentation_services_and_commands.html b/li.strolch.website/www.strolch.li/documentation_services_and_commands.html new file mode 100644 index 000000000..e794ceff5 --- /dev/null +++ b/li.strolch.website/www.strolch.li/documentation_services_and_commands.html @@ -0,0 +1,216 @@ + + + + + + + + + + + + Strolch: Services and Commands + + + + + + + + + + + + +
+ + + +
+ +

Services are written to implement a specific use-case. Commands are written to + implemente 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:

+ +

there are more - check the JavaDocs

+ + +

Commands extend the Command class and then implement the method doCommand(). + Commands have helper + methods:

+ +

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:

+ + +

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 {
+    private static final long serialVersionUID = 1L;
+    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);
+    }
+  }
+}
+            
+ + + + +
+ + + + +
+ + + + + + + + + + + + + +