Difference between revisions of "Commands"

From Developer Documents
Jump to navigation Jump to search
 
(5 intermediate revisions by 2 users not shown)
Line 94: Line 94:
 
way in order to make checking of partial parameters possible. Basically some suffix of the signature
 
way in order to make checking of partial parameters possible. Basically some suffix of the signature
 
is replaced by <code>Boolean</code> and any right subtype may be enclosed in <code>Maybe</code>.  
 
is replaced by <code>Boolean</code> and any right subtype may be enclosed in <code>Maybe</code>.  
The checking function may have ReadGraph event and if it has the command system ensures that it is
+
The checking function may have ReadGraph effect and if it has the command system ensures that it is
 
executed in the read transaction.
 
executed in the read transaction.
 
So, if the signature of the command is
 
So, if the signature of the command is
Line 118: Line 118:
 
</pre>
 
</pre>
 
The command takes a variable and a value in a string form as a parameter.  
 
The command takes a variable and a value in a string form as a parameter.  
The checking function first check, if the variable is writable. The second
+
The checking function first checks if the variable is writable. The second
 
part of the checking tests that the string value is valid.
 
part of the checking tests that the string value is valid.
  
 
== Java API ==
 
== Java API ==
  
Java API for executing commands is meant to be used from during execution of
+
Java API for executing commands is meant to be used during execution of
UI operations. Commands are executed in contexts (usually models) that
+
UI operations. A command is obtained with the following method:
must be first obtained. New command can be created with <code>newCommand</code>
 
method in the context. Parameter <code>name</code> is a full path of the
 
SCL-function implementing the command.
 
 
<pre>
 
<pre>
public class CommandContext {
+
public class Commands {
     public static CommandContext getContext(Resource resource);
+
     public static Command get(ReadGraph graph, String name);
    public static CommandContext getContext(RequestProcessor graph, Resource resource);
 
    public Command newCommand(String name);
 
 
}
 
}
 
</pre>
 
</pre>
 +
The parameter 'name' is the full name of the SCL-function (such as <code>Simantics/Diagram/setTransformation</code>).
 +
The method never fails but returns a dummy Command-object if the command is not found.
  
Command interface has methods for adding and removing parameters, checking
+
Command interface contains a method for checking if the command is applicable with the given parameters. It may be given less than all parameters of the command. The command can be executed synchronously or asynchronously.  
that command is eligible for exectution and committing the command.
+
All methods need a context resource (usually a model) that must be first obtained.  
Even if checking accepts the parameter, the command may fail in commit.  
 
On failure, an exception is thrown (currently no special exception is specified).
 
 
<pre>
 
<pre>
 
public interface Command {
 
public interface Command {
     Command addParameters(Object ... parameters);
+
     boolean check(RequestProcessor processor, Resource model, Object ... parameters) throws DatabaseException;
     Command removeParameters(int count);
+
     Object execute(RequestProcessor processor, Resource model, Object ... parameters) throws DatabaseException;
    boolean check();
+
     void asyncExecute(RequestProcessor processor, Resource model, Object[] parameters,
    boolean check(RequestProcessor graph);
+
            Procedure<Object> procedure);
     void commit();
 
    void commit(RequestProcessor graph);
 
 
}
 
}
 
</pre>
 
</pre>
(TODO async methods?)
 
  
 
The interface handles roles automatically. When the current user has no
 
The interface handles roles automatically. When the current user has no
 
privileges to execute the command, check method always fails.
 
privileges to execute the command, check method always fails.
 
Example (fictional):
 
<pre>
 
public Runnable renameContribution(Resource target) {
 
    final Command command = CommandContext.getContext(target)
 
                .newCommand("Simantics/FictionalModule/rename", target);
 
    if(!command.check())
 
        return null; // Cannot rename the target
 
    else
 
        return new Runnable() {
 
            public void run() {
 
                new RenameWidget(command).start();
 
            }
 
        };
 
}
 
 
public class RenameWidget {
 
    Command command;
 
    public RenameWidget(Command command) { this.command = command; }
 
 
    public boolean validate(String name) {
 
        boolean result = command.addParameters(name).check();
 
        command.removeParameters(1);
 
        return result;
 
    }
 
 
    public void finish() {
 
        try {
 
            command.addParameters(getName()).commit();
 
        } catch(Exception e) {
 
            show error to user
 
        }
 
    }
 
}
 
</pre>
 
  
 
== Recording ==
 
== Recording ==
Line 205: Line 162:
 
named <code>context</code> is defined.
 
named <code>context</code> is defined.
  
Command parameters are serialized with a type class:
+
Command parameters are serialized with a function:
 
<pre>
 
<pre>
class RepresentableParameter a where
+
gshow :: GShow a => Resource -> a -> <Proc,ReadGraph> String
    representParameter :: Resource -> a -> <ReadGraph> String
 
 
</pre>
 
</pre>
 
The first parameter is the current context resource. All references are made relative
 
The first parameter is the current context resource. All references are made relative
to that resource that commands can later executed in some other context. The method
+
to that resource so that commands can later executed in some other context. The method
<code>representParameter</code> works much like <code>show</code> in Prelude, but
+
<code>gshow</code> works much like <code>show</code> in Prelude, but
 
is also able to write expressions for finding resources in ontologies and under
 
is also able to write expressions for finding resources in ontologies and under
the context resource.
+
the context resource. It is defined in the module <code>Simantics/GShow</code>.

Latest revision as of 12:28, 22 October 2013

Requirements

The core idea of Simantics command system is to have all user interface operations go through SCL function calls. This ensures they are all available in SCL for writing scripts and regression tests. The direct user need driving the development is recording of the commands for later replaying.

Because recording and scripting are the main applications, the commands are defined to be operations that make modifications to the persistent state of the application without any user interaction. Thus for example a function that opens a wizard is not a command, but the modification to a model that is executed when the wizard is finished is a command. There are probably cases where the difference is not so clear (is changing of profiles a command?).

For command recording, each command must have a context where it is executed. Particularly when making modifications to two models in the same workspace, the commands should be recorded to two separate command logs. This may cause problems, when the command affects the existence of the model (creating new model, importing model, removing model), the command affects multiple models (moving stuff between models) or the command causes a persistent effect outside of models (changing preferences, importing ontologies).

The command system should specify how commands are executed from Java code, how they are referred in modelled user inteface actions (such as modelled context view or selection view) and how new commands are contributed.

The main challenge of the system is the difference between two modes of execution. When writing a script, the state of the application at execution time is unknown and all commands should be available. Each command is executed atomically and they either succeed or fail with an error message that is either written to the console, if the execution is initiated there, or otherwise shown in a dialog. On the other hand, when executing commands from UI, the user expects that UI hides or disables the actions that are not allowed in the current operating context or will surely fail.

The second challenge is the serialization of the commands for recording so that commands would be valid with maximal probability when replaying in an application context with possible minor differences.

Design

Introduction

The most important thing of the command is the modification it makes to the model. However, for use in UI, some additional information is needed such as which roles are allowed to execute the command and a function to check is the command execution possible.

The following approaches were consided for bundling this information together:

  1. Commands are SCL functions returning a value of type Command a that can be checked and executed
  2. Commands are concepts in ontologies that define different properties containing SCL functions for checking and execution
  3. Commands are specified as separate SCL functions that are tied together by naming conventions

The first approach has some drawbacks. First, it is not possible to know the allowed roles without first giving all parameters to the function. Second, without adding some new syntactic sugar, the Command -type complicates writing of scripts. Either every command has to be wrapped like

exec (newDiagram ...)

or written in monadic style. A benefit of Command -type might be the ability to create new commands by combinators.

The second approach makes possible to define arbitrary properties for the commands, so it is extensible. Its drawback is the need for a read transaction for obtaining information about commands. It makes it also heavier to define new commands. A command contribution might involve additions in three places: Java code implementing the operation, SCL code importing the Java code and ontology referring to SCL.

The third approach has the flexibility of the ontology approach, but is lighterweight. Role and checking may have some defaults (allowed for any role, no checking), which makes it possible to consider any SCL function as a command. For these benefits we choose this approach.

Defining commands

Commands are defined as ordinary SCL-functions. The name of the command (and thus the defining function) should be in imperative mood (createDiagram, not newDiagram). The command is executed in a write transaction, thus WriteGraph and Proc effects are allowed in the signature.

Additional SCL-functions command_check and command_role specify the related checking function and role. Although we follow camel case naming convention in SCL, in this case the suffixes are separated by _. Role function should return the role name (string). The parameters of the role function are some prefix of the parameters of the command, typically role function does not have parameters at all.

The signature of the checking function is related to the signature of the command in a more complicated way in order to make checking of partial parameters possible. Basically some suffix of the signature is replaced by Boolean and any right subtype may be enclosed in Maybe. The checking function may have ReadGraph effect and if it has the command system ensures that it is executed in the read transaction. So, if the signature of the command is

A -> B -> <WriteGraph> C

then, the signature of the checking function can be for example:

Boolean
A -> <ReadGraph> Boolean
A -> B -> <ReadGraph> Boolean
A -> <ReadGraph> Maybe (B -> Boolean)
A -> <ReadGraph> Maybe (B -> <ReadGraph> Boolean)

If the final Boolean value is false or any of the intermediate Maybe value is Nothing, then the command may not be executed.

Here is an example:

setPropertyAsString :: Variable -> String -> <WriteGraph> ()
setPropertyAsString_check :: Variable -> <ReadGraph> Maybe (String -> <ReadGraph> Boolean)
setPropertyAsString_role :: String

The command takes a variable and a value in a string form as a parameter. The checking function first checks if the variable is writable. The second part of the checking tests that the string value is valid.

Java API

Java API for executing commands is meant to be used during execution of UI operations. A command is obtained with the following method:

public class Commands {
    public static Command get(ReadGraph graph, String name);
}

The parameter 'name' is the full name of the SCL-function (such as Simantics/Diagram/setTransformation). The method never fails but returns a dummy Command-object if the command is not found.

Command interface contains a method for checking if the command is applicable with the given parameters. It may be given less than all parameters of the command. The command can be executed synchronously or asynchronously. All methods need a context resource (usually a model) that must be first obtained.

public interface Command {
    boolean check(RequestProcessor processor, Resource model, Object ... parameters) throws DatabaseException;
    Object execute(RequestProcessor processor, Resource model, Object ... parameters) throws DatabaseException;
    void asyncExecute(RequestProcessor processor, Resource model, Object[] parameters,
            Procedure<Object> procedure);
}

The interface handles roles automatically. When the current user has no privileges to execute the command, check method always fails.

Recording

When a command is executed, the textual form of the command is written to the metadata of the change set. The command metadata is associated with a certain context (usually a model). Although the metadata is enough to reconstruct the whole command history, a snapshot of the history is periodically written to a separate file to save information in the case the whole database corrupts.

The writing of command metadata happens in the commit-method of the Command interface after the change is successfully made the the database and no exception is thrown. Each command is written in a form readily suitable for execution in a script with two exceptions: Command begins with the full name of the command function so that no imports are not expected to be available. It is assumed that a resource named context is defined.

Command parameters are serialized with a function:

gshow :: GShow a => Resource -> a -> <Proc,ReadGraph> String

The first parameter is the current context resource. All references are made relative to that resource so that commands can later executed in some other context. The method gshow works much like show in Prelude, but is also able to write expressions for finding resources in ontologies and under the context resource. It is defined in the module Simantics/GShow.