While the UI is one of the 6 primary
components of SAND, it is not directly considered to be a supporting
technology. While SAND has formal technology interfaces for messaging,
persistency, configuration, control and framework processing (see
nodecommon and the
DataManager), it provides an interface for UI development.
SAND actually provides for UI development at multiple levels. This
document covers each of these levels starting with the fundamentals and
working upwards from there.
Requirements and responsibilities:
There are two primary requirements for a SAND UI:
Meeting these requirements is fundamental to establishing a clear
contract of responsibilities within the deployed application.
To accomplish authentication, the UI makes use of the standard means
relative to the requirements of the application. This can be anything from
a web login form, to a retinal scan, to a game of 20 questions. How this
is done is up to the UI, although the SAND environment does provide basic
default support for webapp authentication.
SAND messaging can be achieved easily using an in-process bridge. In
this approach, the deployment configuration contains a node which is
executing in the same process space as the UI code. The UI retrieves
a direct reference to this node using a
SingletonAccessor and then calls it's generated messaging methods
directly:
To support incoming asynchronous communications, a UI can register
itself with the bridge node for callback. For additional information,
refer to adaptor node deployment
and the messaging overview.
SAND supports authorization at the level of actions, data types, data
instances, data fields, data field values, and other programmatic logic
done by the application
Authorizer. It is the responsibility of the Authorizer to ensure that
no unauthorized data is ever passed to the UI, and that no unauthorized
actions or data are accepted from the UI.
In order to provide a non-frustrating user experience, the UI should in turn:
Obviously this requires close cooperation between the Authorizer node,
and the SAND UI. In SAND this cooperation is achieved through the
AuthFilter, which provides the authorization logic. The AuthFilter is
used directly by both the Authorizer, and the SAND UI, to ensure that the
application is consistent.
Refer to the information filtering use cases for
details on specific authorization scenarios.
When you take the number of structs in an application, and look at writing display, edit, query and collection forms for each one, and then writing authorization filtering for each one, autogenerated code is clearly the way to go. The challenge with SANDForms was to handle this code generation in a way that would:
SANDForms are built from the struct declarations. The generators create display code for the associated struct, update, query and collection messages. The display code targets the specific UI technology, so for a webapp, the code writes XHTML forms. The forms are managed using an extended MVC architecture:
SAND provides fully functional default implementations of these interfaces, which can be extended to provide additional functionality. With the XHTMLFormAdaptor, the generated output can also be post-processed using XSLT or other transformation operations. The example TaskHeapDemo deployment uses this technique with a simplified default transformation template.
Refer to the UIFormContext overview for details
on the SANDForms model.
When the UI issues a query, it needs to consider the maximum number of
matching items that should be returned. While the
UIFormOwner will typically enforce some limitations in its
formFind processing, and the
DataManager is configured with hard constraint settings, the correct
number of return values for a query is a balance of:
If a query returns a complete collection (see the
SandCollectionMessage isComplete method), then a UI can
offer arbitrary sorting of the query results. This is referred to as
result ordering. But due to runtime query processing restrictions,
not all query result collections will be complete. In these cases the UI
must work via dataset pagination.
Dataset pagination refers to traversing the total dataspace via repeated
queries, and is therefore dependent on data ordering. If no
orderBy is specified in the query, then ordering is by
uniqueID, and dataset pagination is handled automatically via
the uniqueIDAfter query field. If orderBy is
specified, then the UIFormOwner must handle data pagination
itself using its knowledge of alternate keys.
A few cases are worth mentioning explicitely to illustrate:
Standard dataset pagination:
The
UIFormManagercalls theUIFormOwnerto process the query. TheUIFormOwnerexecutes the query without specifyingorderBy, and the resulting collection elements are displayed ordered byuniqueID.
While
maxReturnin theUIFormContext.findQuerymessage determines the total size of the returned collection, the number of items which are displayed is determined by theUIFormContext.findCollMaxDisplay. The display starts atfindCollIndexin thefindCollection, and showsfindCollMaxDisplayitems.
Pagination is handled as follows:
- If
(findCollIndex + findCollMaxDisplay)is less than the number of elements in thefindCollection, or thefindCollectionis notcomplete, then the "next" action is enabled in the display.- If the "next" action is selected
- if the collection is complete, or it contains enough elements to display the next page without requerying, then
findCollMaxDisplayis added tofindCollIndexto display the next page.- otherwise the query
uniqueIDAfteris set to theuniqueIDof the last displayed element, theuniqueIDof the first element is pushed onto thefindKeysstack, thefindCollIndexis reset, and the query is reissued to retrieve the next page for display.- If
findCollIndex > findCollMaxDisplay(we are on the second display page of a collection), orfindKeysis not empty, then the "previous" action is enabled in the display.- If the "previous" action is selected
- if
findCollIndexis greater than zero, then it is set to(findCollIndex - findCollMaxDisplay)with the result floored at zero.- otherwise the query
uniqueIDAfteris set to the last value popped off thefindKeysstack minus one, thefindCollIndexis reset, and the query is reissued to retrieve the previous page for display.
Dataset pagination with custom ordering:
When standard dataset pagination detects that it needs to reissue the query in response to either a "next" or "previous" action, it skips the
uniqueIDcalculations iforderByis specified in thefindQuery. Movement within the collection elements is still supported, but when a query needs to be reissued it is not modified by theUIFormManager.
Since the
UIFormManagercalls theUIFormOwnerto process the query, and passes along theUIFormContext, theUIFormOwnerhas all the information it needs to handle custom data pagination. To do this it will need to implement the change to the query, but with the alternate ordering key.
As an example consider custom pagination ordering on the
usernamekey ofBaseUser. Before processing the query, an extra match is added specifyingusernameas greater than the last value in the current collection. This match takes the place of theuniqueIDAfterin standard dataset pagination. In this case thefindKeysfield (used for reverse pagination) would holdusernames rather thanuniqueIDs.
Custom data pagination is relatively easy to implement since the code relating to the existing collection is re-used as a common framework.
Adapting SANDForms to other UI technology:
At the time of this writing, only the XHTMLFormAdaptor is available.
Using SANDForms with other UI technology would require writing other
UIFormAdaptor implementations. For example a SwingFormAdaptor would write
Swing classes to provide forms for the defined structs.
The SandUI framework is designed to be technology independent, and is intended for use with webapps, rich client GUI interfaces, text terminals, restricted display interfaces, or voice interfaces. By autogenerating the user interface from the data structures and the SandUI definition, it is possible to support many application changes without impact to user display rendering, even if the application supports multiple user interfaces simultaneously.
Our next priority is likely to be SWT, but this depends on customer
projects. Our hope is to release these adaptors to open source as the
versions upgrade. If you are interested in specific technologies, joint
development, or extensions to SANDForms, please contact
Structs And Nodes Development Services
Using SANDForms with other UI frameworks:
For new development, we would recommend creating a UIFormAdaptor to
generate the code expected by the UI framework. This can be done using the
XHTMLFormAdaptor as a model, or alternatively using runtime post-processing
(provided the differences in form output are relatively minor).
In situations where there is already significant established UI code, it
may be fastest to build an adaptor node to convert the existing UI form
processing calls to SAND messaging. If new data structures are being added
to an existing webapp, it may be best to segment the application to take
best advantage of code generation to minimize the work involved.
Conversion work is being handled on a per-project basis.
The SANDForms native framework:
The SANDForms native framework is implemented in the apps/ui project and supporting tools. A user interface is defined as an instance of a SandUI, which can be viewed/modified using the UI editor. The interface is then implemented through generated code, which varies depending on the interface technology used.
As one example, here's how the webapp interface of the TaskHeapDemo deployment works:
To handle a given screen request, XHTMLSandUIServlet
That's about it. Authentication and error processing are pretty standard for servlet processing. The only other point of interest is that it is possible to override locale information regardless of server settings, which can be handy. For more details, refer to XHTMLSandUIServlet.java.
model/view/controller (MVC) architecture:
In a typical web browser UI, individual data entry and query forms are placed into pages, and the pages are organized into a web site. As a website grows in size, separating the component aspects of the site becomes increasingly important in managing complexity. MVC is a general UI architecture which helps separate the major component aspects:
So you might have a clock object with getTime and
setTime methods (model), with code to translate the clock
object into an analog or digital display (view), and code to check if the
time was changed and/or if that is allowed (controller).
There are several mature frameworks (both commercial and open source) available for building user interfaces. There is also the default SandUI framework, with form support autogenerated from the struct definitions. Which framework you choose will be based on your application requirements, but regardless of which you choose, the Structs And Nodes Development approach is the same:
How the adaptor node does its work, and what the view generator code needs to do, depends on your application, the interface API, and which framework you are using. SandBoss ships with an example TaskHeapDemo deployment with a simplified user interface that serves as an example for how to get started.
Enterprise information is sensitive, so it is imperative that no
unauthorized information be transmitted to a client machine. Preventing
unauthorized input, and setting unauthorized fields to their default values
is the job of the
Authorizer node. Hiding messages, fields, or values from view is the
job of the UI, since this is where the message is transformed from an object
into a display structure. Both mechanisms use an
AuthFilter to determine the appropriate action to take.
Although different applications have different authorization requirements, the following cases illustrate the types of processing required:
case: hidden message types
User is not allowed access to any form of a specific message. For example
you might have a SiteRevenueSummary message that the vast majority
of users should not even know about.
case: hidden message fields
User is not allowed access to specific fields within a message. For
example you might have tracking information as part of a User
message which is only visible to site administrators.
case: limited update fields
User has read access to fields which they may not edit. For example you
might have a membershipExpiration field as part of a
User message which a user can see, but which can only be
updated by site administrators or by membership payment processing.
case: limited message actions
User may query but not update (or more unusually update but not query). As
an example, users might query for Products but not be allowed
to update them.
case: limited instance updates User may only update specific message instances, or instances matching specific criteria. For example a user may only allowed to update their own user information, or user information for users who they manage.
case: limited instance queries User may only query for specific subsets of the total data available. For example a user may only be allowed to query for orders which they themselves have placed.
case: limited query fields (see hidden message fields).
case: limited query field values User may only specify a subset of all the possible values when issuing a query. For example a user may be allowed to search for new or promotional products, but not archived, deleted, or group limited ones.
case: variably hidden fields When viewing data, the user will see the values for fields in authorized instances only. For example a user may see personal info for their direct reports but not others.
note:
The UIFormContext has four major modes, each of which can be accessed as
an initial state or through standard user actions:
A mode is not a state, since actual function varies depending on
supported actions and context. A description of each mode and transitional
actions follows.
Listing Mode: Initial conditions: Supported actions: notes: Updating Mode: Initial conditions: Supported actions: notes: Adding Mode: Initial conditions: Supported actions: notes: Finding Mode: Initial conditions: Supported actions: notes: To support the modes and actions described above, the
UIFormContext contains The UIFormManager (view implementation) must provide for processing of
user requests by implementing the methods called in response to a user
action. It is expected most applications will leverage either
PlainObjectFormManager or PersistentObjectFormManager through extension or
aggregation/passthrough. In addition to implementing UIFormManager, a UI adaptor node will
typically modify the display results to customize the look and feel for the
application. It is also responsible for providing an AuthUser and
AuthFilter for authorization processing. Under some circumstances, a UI
may also create additional user actions beyond those directly supported by
the UIFormContext. I18n can be handled within result transformation template processing,
loading of localized result transformation templates, customized rendering,
extended form management, or a combination of these approaches. The best
approach for your application depends on your application requirements.
For additional information contact
Structs And Nodes Development Services.
int mode
LISTING | ADDING | UPDATING | FINDING
The mode may be computed from the modePath.
String modePath
Keeps track of mode transitions for ok/cancel operations. This is
represented as dot separated mode strings (for example:
LISTING.UPDATING.ADDING.FINDING.LISTING) with supporting push and pop
operations.
String position
In support of drilldown processing, arrays of references, and arrays of
contained objects, this holds our location within the rootMsg using
standard dot-reference and array element notation.
SandMessage rootMsg
The root message we are working with and possibly moving around in.
SandMessage currInst
holds the current object instance we are operating on.
String currClass
holds the short name of the current type of object we are working with.
boolean pendingEdits
true if there are changes which have not yet been committed.
AggregateUpdate updates
outstanding updates necessary for save processing. When editing a
non-peristent object, changes may be rolled into the rootMsg directly,
in which case this field will be null.
SandCollectionMessage findCollection
The collection returned from our query (FINDING mode)
int findCollIndex
holds the starting index of the collection elements to display
(FINDING mode)
int findCollMaxDisplay
holds the maximum number of collection elements to display (FINDING mode)
SandQueryMessage findQuery
holds the query that generated this collection (FINDING mode)
String findKeys
holds a stack of key values for reverse data pagination traversal
(FINDING mode)
long userID
holds the unique ID of the current user for ease of retrieval during
authentication/authorization of each request
int action
holds enumerated action which was taken by the user.
boolean[] supportedActions
a flag array indicating which actions are supported. Marking an action
as unsupported removes that action from the user. This is useful for
limited displays
UIQuery userQuery
Used for disambiguation of class types or other user dialog required for
processing
String outputText
Used for error and informational output during processing
boolean allowEditReadOnly
false unless things like immutable IDs are allowed to be changed (such
as when editing initial data)
All Rights Reserved.