A service context is a dictionary composed of path-value pairs, i.e., associations, such that each path referring to its value appears at most once in the context. A path of each association is an implicit invocation parameter. Everything that has an independent existence is expressed in the service context as an association (entry). Additional properties with a context path can be specified giving procedural meaning to the active value referred by its path. Service contexts with constant values (not evaluated objects) are called data contexts or data models and those containing active data (evaluated objects) are called context models. Context-aware computing defines a service as a mapping with the property that a single input context is related to exactly one output context.

The context attributes forming paths form a taxonomic tree, similar to the relationship between directories in file systems. Paths in the taxonomic tree are names of implicit service arguments or free variables. Each service has a single data context as the explicit argument. Paths of the data context form implicit domain specific inputs and outputs used by service providers. Context input associations are used by service providers to compute output associations that are returned in the output context. For completeness, the output context usually extends or complements the input context, so it also contains all input entries that produce output entries.

The context mapping is specified by a service signature that includes at least the name of operation (selector) and the service type implemented by the required service provider. Additionally, the signature may also specify the service’s return path, the type of returned value, and the name of required provider in the network. Also complementary QoS attributes can be provided as well. Compound services have hierarchically organized data contexts and control contexts for collaborating service providers in the network.

The Service Modeling Language (SML) is a form of declarative programming language where operators are key elements of the language and the emphasis is on composing operators and not so on issuing commands. SML that consists of two parts: Context Modeling Language (CML) and Exertion-Oriented Language (EOL). The basic service operator declares a service model that can be evaluated with value or exerted with exert operators. The data context for a service is declared with the context operator

The notation for SML syntax rules, that are used occasionally, are illustrated here with undefined nonterminal symbols in angle brackets <symbol>. Text as code operator signifies literal terminals, mostly SML operators and Java expressions. Each SML expression is a combination of nonterminal and terminal symbols with arguments of operators separated with comma. Other metasymbols are: | for alternation, parenthesis ( … ) for grouping, […] for 0 or 1 occurrences, {…} for 0 or more occurrence, ; for the rule termination, and : preceding a type of returned value..

Create a Context

A data context is declared with the context operator and a list of entries, for example:

   Context<Double> cxt = context(ent("arg/x1", 1.1), ent("arg/x2", 1.2),
                    ent("arg/x3", 1.3), ent("arg/x4", 1.4), ent("arg/x5", 1.5));

A value of entry is received with the get operators:

   assertTrue(get(cxt, "arg/x1").equals(1.1));

Multiple values can be extracted as a subcontext for a given list of paths:

   Context<Double> subcxt = context(cxt, list("arg/x4", "arg/x5"));

A context can be expended by adding additional entries:

   add(cxt, ent("arg/x6", 1.6));

A context can updated with a new value:

   put(cxt, ent("arg/x1", 2.1));

A context can be of reactive value type with the reactive value entry operator rvEnt:

    // aliasing with an reactive value entry - rvEnt
    put(cxt, rvEnt("arg/x1", value(cxt, "arg/x5")));
    assertTrue(get(cxt, "arg/x1").equals(1.5));

Common entries (ent operator) return value asis but reactive value entries return tobe values. In other words, values of entries that are of Evaluation type return the result of their evaluation in rvEnt but the Evaluation itself in common entries.

Directional Entries

Each context entry can be considered as a associative variable, where a taxonomic path is its semantic name associated with its value. There are many types of entries: common entries (ent operator), input entries (inEnt operator), output entries (outEnt operator), input/output enties (inoutEnt operator). An entry declared with a path only has its value undefined, that is Context.none. Understanding the context taxonomy and the semantics of entry types a provider can retrieve from its context, for example, all input, output, inout entries, or entries marked with domain specific relationships. Above, the operators context, add, value, get, asis, put illustrate how a context is created and hpw its data can be used and updated in service contexts.

Contexts are updated with add and put operators in the form:

    put(<context> {, entry(<path>, <value>)}):Context
    add(<context> {, entry(<path>, <value>)}):Context

Context are arguments for all provider operations and return values. A context of an elementary exertion is obtained with the context operator as follows:


An aggregated context of compound exertion (job and bloc) as follows:


A context of a component exertion at a composition path:

    context(<exertion>, <component path>)

Having a context its subcontext can be received by listing path of a required subcontext as follows:

    get(<context> {, path}):Object

Marking Contexts

Marking entries of a context is a kind of relational annotation with the name of a relation and domain attributes describing how certain things may be connected in some way to the marked entry. For example a default relation triplet defines the association triplet|_1|_2|_3 where triplet is a ternary relation associating a set of triples in domains _1, _2, and _3. Any instance of association (relation name triplet with a triplet of value in the product _1 x _2 x _3) defined by this relation e.g., triplet|a|x|x can be used to mark an entry of a context.

Marking an entry with a path arg/x1 of a context cxt is done with the mark operator as follows:

    mark(cxt, "arg/x1", "triplet|a|x|x");

To get a value of the marked entry is done with the getAt operators as follow:

    assertTrue(getAt(cxt, "triplet|a|x|x").equals(1.1));

An unary (tagging with the attribute tag), binary, and ternary relations are defined for context as follows:


These relations and custom relations can be defined and used as illustrated in the examples below:

    Context<Double> cxt = context(ent("arg/x1", 1.1), ent("arg/x2", 1.2),
         ent("arg/x3", 1.3), ent("arg/x4", 1.4), ent("arg/x5", 1.5));

    add(cxt, ent("arg/x6", 1.6));
    add(cxt, inEnt("arg/x7", 1.7));
    add(cxt, outEnt("arg/y", 1.8));

    // the default marker (attribute) 'tag'
    mark(cxt, "arg/x1", "tag|set1");
    mark(cxt, "arg/x2", "tag|set1");
    assertEquals(valuesAt(cxt, "tag|set1"), list(1.2, 1.1));

    mark(cxt, "arg/x2", "tag|set2");
    mark(cxt, "arg/x4", "tag|set2");
    assertEquals(valuesAt(cxt, "tag|set2"), list(1.4, 1.2));

    // now the path "arg/x2" is overwritten, so excluded
    assertEquals(valuesAt(cxt, "tag|set1"), list(1.1));

    // the default relation 'triplet', the association:  "triplet|_1|_2|_3"
    mark(cxt, "arg/x1", "triplet|a|x|x");
    mark(cxt, "arg/x2", "triplet|x|b|x");
    mark(cxt, "arg/x3", "triplet|x|x|c");
    assertTrue(getAt(cxt, "triplet|a|x|x").equals(1.1));
    assertTrue(getAt(cxt, "triplet|x|b|x").equals(1.2));
    assertTrue(getAt(cxt, "triplet|x|x|c").equals(1.3));

    mark(cxt, "arg/y", "dnt|engineering|text|mesh");
    assertTrue(getAt(cxt, "dnt|engineering|text|mesh").equals(1.8));

    // still the previous marker 'tag' holds with 'triplet'
    // for the same paths: arg/x1 and arg/x2
    assertEquals(valuesAt(cxt, "tag|set2"), list(1.4, 1.2));

    // custom annotation with the association: "person|first|last"
    marker(cxt, "person|first|last");
    add(cxt, ent("arg/Mike/height", 174.0, "person|Mike|Sobolewski"));
    add(cxt, inEnt("arg/John/height", 178.0, "person|John|Doe"));
    assertTrue(getAt(cxt, "person|Mike|Sobolewski").equals(174.0));
    assertTrue(getAt(cxt, "person|John|Doe").equals(178.0));

Linked Contexts

    Context ac = context("add",
           inEnt("arg1/value", 90.0),
           inEnt("arg2/value", 110.0));

    Context mc = context("multiply",
            inEnt("arg1/value", 10.0),
            inEnt("arg2/value", 70.0));

    Context lc = context("invoke");

    link(lc, "add", ac);
    link(lc, "multiply", mc);

    ac = context(getLink(lc, "add"));
    mc = context(getLink(lc, "multiply"));

    assertEquals(value(ac, "arg1/value"), 90.0);
    assertEquals(value(mc, "arg2/value"), 70.0);

    assertEquals(value(lc, "add/arg1/value"), 90.0);
    assertEquals(value(lc, "multiply/arg2/value"), 70.0);           

List Contexts

Lists of elements can be represented with Java List API as indexed contexts:

    // ListContext complies with Java List API
    ListContext<Double> cxt = listContext(1.1, 1.2, 1.3, 1.4, 1.5);

    assertTrue(cxt instanceof Context);

    assertEquals(cxt.get(1), 1.2);

    cxt.set(1, 5.0);
    assertEquals(cxt.get(1), 5.0);

    assertEquals(cxt.values(), list(1.1, 5.0, 1.3, 1.4, 1.5));

    cxt.set(1, 20.0);
    assertEquals(20.0, cxt.get(1));

    assertEquals(cxt.add(30.0), true);
    assertEquals(cxt.get(5), 30.0);

Above the operations: get and set are used with ListContext indexes the same ways as with Java lists.

Data Context Pipes

    Task f4 = task(
         sig("multiply", Multiplier.class,
         context("multiply", inEnt("arg/x1", 10.0d),
              inEnt("arg/x2", 50.0d), outEnt("result/y1", null)));

    Task f5 = task(
         sig("add", Adder.class,
         context("add", inEnt("arg/x3", 20.0d), inEnt("arg/x4", 80.0d),
              outEnt("result/y2", null)));

    Task f3 = task(
         sig("subtract", Subtractor.class,
         context("subtract", inEnt("arg/x5", null),
              inEnt("arg/x6", null), outEnt("result/y3", null)));

    // job("f1", job("f2", f4, f5), f3,
    // job("f1", job("f2", f4, f5, strategy(Flow.PAR, Access.PULL)), f3,
    Job f1 = job("f1", job("f2", f4, f5), f3, strategy(Provision.NO),
         pipe(outPoint(f4, "result/y1"), inPoint(f3, "arg/x5")),
         pipe(outPoint(f5, "result/y2"), inPoint(f3, "arg/x6")));

Shared Context Values

    // two contexts ac and mc sharing arg1/value 
    // and arg3/value values over the network
    Context ac = context("add",
         inEnt("arg1/value", 90.0),
         inEnt("arg2/value", 110.0),
         inEnt("arg3/value", 100.0));

    // make arg1/value persistent
    URL a1vURL = storeArg(ac, "arg1/value");

    // make arg1/value in mc the same as in ac
    Context mc = context("multiply",
         dbInEnt("arg1/value", a1vURL),
         inEnt("arg2/value", 70.0),
         inEnt("arg3/value", 200.0));

    // sharing arg1/value from mc in ac
    assertTrue(value(ac, "arg1/value").equals(90.0));
    assertTrue(value(mc, "arg1/value").equals(90.0));

    put(mc, "arg1/value", 200.0);
    assertTrue(value(ac, "arg1/value").equals(200.0));
    assertTrue(value(mc, "arg1/value").equals(200.0));

    // sharing arg3/value from ac in mc
    assertTrue(value(ac, "arg3/value").equals(100.0));
    assertTrue(value(mc, "arg3/value").equals(200.0));
    URL a3vURL = storeArg(mc, "arg3/value");
    add(ac, ent("arg3/value", a3vURL));

    put(ac, "arg1/value", 300.0);
    assertTrue(value(ac, "arg1/value").equals(300.0));
    assertTrue(value(mc, "arg1/value").equals(300.0));

Shared Contexts

    Context cxt = context("work", 
         ent("req/name", hostname),
         ent("req/arg/1", 11),
         ent("req/arg/2", 101),
         ent("req/work", Works.work0),
         ent("to/prv/name", "SORCER Worker"));

    contextUrl = store(cxt);

    task("work", sig("doWork", WorkerProvider.class), context(contextUrl));

Context Models

    // create a context model - a context with evaluated entries
    // or convert any context with entModel(<context>)
    Context<Double> cxt = entModel(ent("arg/x1", 1.0), ent("arg/x2", 2.0),
         ent("arg/x3", 3.0), ent("arg/x4", 4.0), ent("arg/x5", 5.0));

    add(cxt, ent("arg/x6", 6.0));
    assertTrue(value(cxt, "arg/x6").equals(6.0));

    // ent is of the Evaluation tpe
    put(cxt, ent("arg/x6", ent("overwrite", 20.0)));
    assertTrue(value(cxt, "arg/x6").equals(20.0));

    // invoker is of the Invocation type
    add(cxt, ent("arg/x7", invoker("x1 + x3", ents("x1", "x3"))));
    assertTrue(value(cxt, "arg/x7").equals(4.0));

Context Dependencies

A context may depend on other actions specified bt the Evaluation interface. A list of Evaluation dependers can be added to a context with the dependsOn operator as follows:

    dependsOn(context, depender1, depender2, ...);

as illustrated in the example below:

    Context<Double> cxt1 = entModel(ent("arg/x1", 1.0), ent("arg/x2", 2.0),
         ent("arg/x3", 3.0), ent("arg/x4", 4.0), ent("arg/x5", 5.0));
    add(cxt1, ent("y1", invoker("x1 + x3", ents("x1", "x3"))));
    add(cxt1, ent("y2", invoker("x4 * x5", ents("x1", "x3"))));

    // cxt2 depends on values y1 and y2 calculated in cxt1
    Context<Double> cxt2 = entModel(ent("arg/y3", 8.0), ent("arg/y4", 9.0), ent("arg/y5", 10.0));
    add(cxt2, ent("invoke", invoker("y1 + y2 + y4 + y5", ents("y1", "y2", "y4", "y5"))));
    target(cxt2, "invoke");

    // created dependency of cxt2 on cxt1 via a context copier
    Copier cp = copier(cxt1, ents("arg/x1", "arg/x2"), cxt2, ents("y1", "y2"));
    dependsOn(cxt2, cp);

    Double result = value(cxt2);
    assertTrue( value(cxt2).equals(22.0));

where a depender copier copies entries from cxt1 into cxt2 before a value of cxt2 is received at its target invoke.

A Context as a Service Provider

Service Context JUnit Tests

Review the wide range of service context examples in examples/sml/src/test/java/sorcer/sml/contexts used for testing the SORCER Project.

Back to top

Version: 1.0-SNAPSHOT. Last Published: 2020-01-18.