Subsystem spiralcraft.lang

Related Topics

Overview

ELaine (spiralcraft.lang) is an expression language system designed to bind application components together across multiple type systems that serves as a primary integration hub within the Spiralcraft Framework.

The EL can navigate beans, static methods and metadata in the Java class namespace and data models exposed by spiralcraft.data, according to the contextual model provided by a container.

Containers, such as the built-in spiralcraft.builder and spiralcraft.webui or special purpose and add-on implementations, define the set of objects reachable from the EL via the Focus chain.


Expressions

An Expression is a language construct with a Java-like syntax used to navigate object models in pluggable type systems. The syntax is designed to be as fluent as possible and to keep the framework "plumbing" relatively invisible to the scripter, so that a scripter familiar with a problem domain model can express relationships and computations in a concise and direct manner.

Expressions are primarily used inside of application component definitions (ie. spiralcraft.builder and inside content generation templates (ie. spiralcraft.textgen). They can be used within non-Spiralcraft containers as well, or can be integrated into "expert level" user interfaces using the spiralcraft.lang API.

Some simple examples of Expressions (more examples):

Add two literal integer values

1+1

Add the value of property "x" to the value of property "y"

x+y

Get the value of the "customer" property from the subject of the expression Focus

customer

Get the name of the customer

customer.name

Get the customer's orders that are over $100.00

customer.orders[.amount>100.00]

Obtain the contextual Customer (assuming our local context involves some "selected" Customer, and that the myns namespace is mapped to the location of the Customer type)

[myns:Customer]

Implementation Specifics

The expression language itself is an LL(2) grammar implemented using a recursive descent parser.

Type Models

A Type Model is a pluggable mechanism that defines the interpretation of the various language constructs in an Expression. This permits advanced developers to apply the Expression syntax to their own language representations by implementing an appropriate TypeModel.

Type Models exist because domain specific type systems are not always implemented via a one-to-one correspondence with the Java type system- ie. a separate Java Class for each domain specific type, and a set of "accessor" methods for each attribute of the domain specific type. The following scenarios illustrate this issue:

  • A domain specific type model may be implemented at a low level, with finer granularity than the domain type model- ie. the Java objects are expressed on the level of the storage implementation, and not the domain. For example, a DOM type of data structure might be used to represent objects stored in XML data. The Java objects utilized would be things like "Node" and "Attribute", which would contain names and values.
  • A domain specific type model may not be "implemented" in Java at all, in the case where data is represented as a large binary object, and a direct mapping to data regions is the preferred access method.
  • A domain specific type model may be implemented in an external system, such as a SQL database, where it must be accessed in an ad-hoc manner at runtime.

In the above scenarios, a Type Model can be created to map the type, property, and metadata naming mechanisms of the Expression language to the appropriate implementation constructs.


Reflection Type Model

The spiralcraft.lang subsytem comes with the spiralcraft.lang.reflect Type Model, which is a built-in, foundation model that can access properties defined via the Java "getter/setter" mechanism, can call Object and Class methods, and can performs other metadata operations using the java.lang and java.lang.reflect APIs.

This model is used for most "configuration" level tasks, and as a fallback for extended Type Models that wish to expose Java objects.

Data Type Model

The spiralcraft.data subsystem provides a Type Model to navigate relational data objects in Tuple form. The underlying Java object representation of this data is generally some form of Tuple or Aggregate "active record", but the rich type model of spiralcraft.data is used to map methods and properties.

This Type Model provides facilities to enable concise, fluent expressions such as the following expression which retrieves open orders for a given customer that contain line-items that refer to back-ordered stock.

customer.orders[open && .items[.stock.backOrdered].@size > 0]

The properties orders, items, and stock are defined as keyed relationships.

Focus

A Focus represents the environment into which an Expression is bound. It provides access to a data source, referred to as the "subject" of the Focus, that supplies typed data against which the expression will be evaluated.

A Focus is supplied by the various components which support expression evaluation.

For example, if the subject of a Focus is a "customer" object, the following simple expression returns the last name of the customer.

 lastName

and the following simple expression returns the customers "full name"

 (title!=null?title+" ":"")+firstName+" "+lastName

Focus Chain

A Focus often refers to a parent Focus, which represents an enclosing context. The parent Focus, in turn, often refers to another parent Focus, which represents a broader enclosing context. The Focus Chain refers to this list of Focii, and defines the set of data sources visible to an expression.

For example, if the subject of a Focus is an "order", and the parent focus is a "customer", the following simple expression, expressed in relative terms, indicates whether the order is over the customer's threshold amount. The ".." refers to the subject of the parent Focus.

 amount>..threshold

Focus Resolution

Another way to express the same expression, in absolute terms, is useful when we know that there is a customer and an order somewhere up the Focus chain, but not necessarily in the local Focus. The following is an example of Focus Resolution, and assumes that the namespace "mytypes" is mapped to the location of Customer and Order type definitions.

 [mytypes:Order].amount > [mytypes:Customer].threshold

Focus Resolution implements a Contextual Singleton design pattern, and permits pluggable components to be created that do not require explicit deployment configuration to resolve their dependencies.

TeleFocus

The namespace against which unqualified identifiers (names with no prefix) in an expression are resolved is called the "context". The Focus context is usually the same as the Focus subject.

In some instances within a single expression, such as when processing the members of a collection using the "[...]" operation, or generally for iterative operations, it is necessary to compare an item being operated on to the context of the local Focus.

A TeleFocus refers to a Focus where the subject refers to an item that is changing, and the context refers to the subject of the Focus of expression evaluation. Members of the local subject of the TeleFocus are referenced using the "." (dot) prefix.

For example, the following expression returns the set of Orders for a Customer with an amount over the threshold, evaluated in the context of a Customer object. The .amount refers to a property of the Order being evaluated, and the threshold refers to a property of a Customer. The "[...]" operation accepts an expression and creates a TeleFocus to evaluate the internal expression.

 orders[.amount>threshold] 

Channels and Reflectors

An Expression is parsed from a textual representation or constructed programmatically at load-time into a form of parse tree, or data structure that mirrors the expression syntax. This form does not include any contextual information, and cannot yet be evaluated (although it can be examined). An Expression is thus an abstract construct that represents the intent of the writer.

When an Expression is bound to a Focus, it resolves the referenced data source names, types, and operations from the context provided by the Focus, creating a directed acyclic graph (DAG) of Channels which implements the specified computation.

Channels

A Channel is a concrete implementation of an atomic data transformation that is bound to one or more data sources. An Expression that contains multiple transformations (most do) creates a graph of Channels when bound to a Focus that mirror the structure of the Expression, referred to as a Channel DAG (directed acyclic graph).

A Channel has two primary methods, "get" and "set". (A "listen" operation is also supported at this time, albeit with some syntax revision). A call to get() effectively evaluates the Expression associated with the channel against the data source(s) to which the channel is bound. A call to set() reverses the transformation and pushes a value back to the data source(s).

For example, the following Expression can be used to bind, say, a city selector component to a customer's address city. This Expression creates a bidirectional channel that can be used to retrieve and update the value customer's city, assuming that there is a "mytypes:Customer" object is available in the Focus chain.

[mytypes:Customer].address.city

The operation of Channels is invisible to the Expression writer, but is one of the primary APIs (along with the Focus API) by which Expression containers support the use of Expressions to script their components.

Threading and Parallelism

A Channel is stateless by nature, and is thus thread-safe. If the data sources to which a Channel graph is bound are also thread-safe, a single Channel network can support multi-threaded operation.

A ThreadLocalChannel implementation provides a means to associate a unique data value with a single thread context, with the option of the value being inheritable by child threads. A shared Channel network bound to ThreadLocalChannels as their stateful data sources is capable of parallel operation with minimal or no synchronization overhead.

The WebUI subsystem takes full advantage of this mechanism by maintaining a single Channel graph for each template resource which can inherently process an arbitrary number of requests in parallel, subject only to hardware/os limitations and the synchronization requirements of shared resources.


Reflectors

A Reflector, created as part of some Type Model, provides the metadata based mechanism by which the identifiers, methods, and other operations in Expressions are resolved to Channels. Specifically, the Reflector takes a Focus, a source channel, a name token, and a set of parameter Expressions and returns a Channel bound to the Focus that implements the specified transformation.

For the built-in Java beans Type Model, an instance of a Reflector is created for every Java class referenced by the Expression language.

For the built-in spiralcraft.data type model, and instance of a Reflector is created for every defined type reference by the Expression language.

Typically, 1-3 custom Reflector classes are needed to fully expose a type model. For the built-in type models, one Reflector class handles the primary types, and other Reflector classes are used for various array and collection types. These differentiations are only visible to type model implementers.