Facets - Web Application Framework

Copyright (c) 2005 Tom Bradford (bradford 653 [at] gmail [dot] com)

Introduction

Facets is a web application framework that leverages a simple MVC architecture for the server side and a feature-packed Javascript component model for the client. The two pieces are designed to be seemlessly integrated for very dynamic AJAX-style apps.

Goals of the Facets Project

The overwhelming goals of the Facets project follow:

How does Facets work?

The server side architecture is a simple MVC system, designed to be much simpler than Struts, and more closely resemble the Message/Method paradigm supported by the Cocoa Framework. It is written in Java and can be deployed in any Servlet container.

The client side architecture is a Javascript library that allows for the dynamic augmentation of web pages using an extensible Javascript component model. By a designer tagging elements with IDs, this component model can identify its presentation analog, and allow a developer to manipulate the DOM in a programmer-friendly way, similar to VB or Delphi, without having to understand the DOM or the failure of most browsers to consistently implement the DOM.

The client side libraries also allow for the definition of reusable components, which can either be stored on the page that hosts them, or in an external page that will be resolved by the library.

The client side becomes a very dynamic interface, where page refreshing is reduced to a minimum, and data exchange is performed in an asynchronous fashion using an XML data exchange protocol.

The server side exposes heterogeneous storage backends as a common datamodel interface which can be leveraged both by the client side and the server side using identical APIs.

Current Status

This project is currently designated as being in the early stages of development. Attention will initially be paid to the development of the server-side architecture, with the client side libraries evolving as necessary based on the server's progress.

Facets Architecture

Facets' architecture has been designed to be mostly imperative, modular, and nested, as opposed to the very declarative and hard to follow design of schemas such as the one employed by Struts.

This is a current diagram of the Facets configuration schema. It is, for the most part, analogous to the architectural model of the system itself. The only important piece missing from the following diagram is the Message class, which is discussed below.

Message instances are passed around extensively in the Facets architecture, both to Method calls and to Page processing. A Message is a Java class that encapsulates several aspects of the current Servlet request. This includes the Request and Response objects themselves, as well as a set of optional attributes. The benefit of passing around a Message class is that the payload is flexible, and there is no fear of having to make API signature changes in the future.

Definitions

FacetsConfig

FacetsConfig is the top level container for all Facets configuration information. It is also the top-level container in the Facets architecture.

Imports

Imports allow the Facets configuration file to import definitions and modules from additional files. Providing this capability in the Facets configuration file reduces the amount of customization to the Servlet container's web.xml file.

Types

Types allows you to define aliases for implementation classes throughout the configuration framework. This avoids the class name hell that is strewn about a Struts configuration file.

DataModels

Datamodels define a very simple, implementation agnostic API for accessing data in a Key Path style manner. DataModels are capable of being both represented and transported in whole or in part both within the Server and on the Client side.

Methods

Methods are similar to Actions in the Struts architecture, save for some slight differences. Methods don't return a value in Facets, and are generally passive, meaning their execution is entirely managed and controlled by the Facets Page. In order to short circuit this behavior, an Invocation instance can be thrown.

Global Methods defined at the top level of the configuration are called when a Page or Method instance invokes a Method from its current contexts, and must fall back to the root context for Methods. Good Methods to put here are ones for handling logins and unexpected errors. Remember that these Methods can be overridden in nested contexts.

Pages

Pages encapsulate the ability to generate content to be consumed by the browser. They can be contextually nested so that a Page may contain zero or more child Pages. This helps to keep Pages and Methods that are associated with a major functional area close to each other. It also lessens the burden of disambiguating names, as a Page may define its own 'submit', 'cancel' methods without worry that another Page has done the same.

Pages can have a general purpose constructor by defining a Method called 'init' in the local context of the Page. This constructor will only be called if it's part of the Page's local context, will always be called before the method map is resolved. As a result, a constructor can be used to influence the mapping by altering the Message state.

MethodMap

MethodMap maps the various incoming states of a request to a particular Method call. When a proper Method has been selected, the Router sends a Message to it. This Message encapsulates the various parameters of the Request/Response sequence. Multiple Methods can be chained using the SequenceMethod type.

MethodMaps, as their name would indicate, only map the Message state to Method calls. In developing a simplified architecture, it was decided that it would be best to avoid overloaded multifunctional tags, and the 'forward' and 'redirect' hell that has confused quite a few developers while working with other systems. In order to redirect, you map to a Method that is an instance of PageMethod or RedirectMethod.

Data Models

A Facets DataModel is materially represented as a JavaScript object graph both on the server and client sides. This object graph is transported utilizing a serialized combination of JavaScript's literal syntaxes including Object, Array, and Scalar literal syntax. Function evaluation may be leveraged, but canonical retention is not maintained in that case.

Example:

dataModels.contactInfo = {
    firstName: "Tom",
    lastName:  "Bradford",
    birthDate: "1972-05-09"
};

dataModels.homeAddress = {
    street1: "123 Main Street",
    street2: "",
    city:    "Anytown",
    state:   "MA",
    zipCode: "02128"
};

On the server side, care is taken to manage data models only within isolated contexts so as to avoid any possible security issues that might arise due to misuse or negligence. Any function references and expressions in the literal syntax are resolved within a completely isolated context before being handed to any manipulating JavaScript code.

On the client side, no such guarantees about data isolation can be made, but because the server resolves function references and expressions as they're encountered in a DataModel write, chances are that no function references or expressions will find their way to the client's serialization of a DataModel.

Because DataModels are materialized as JavaScript object graphs, standard JavaScript referencing can be used to traverse and manipulate these graphs both on the server and client sides.

DataModel schemas are declared as instantiations of classes in the Facets JavaScript SchemaType hierarchy. These include ObjectType, ArrayType, and various forms of ScalarTypes such as NumericType, StringType, and DateTimeType. Of course, these instances can be leveraged both on the server and client sides, allowing for schema validation on either end of the transaction.

Example of a contactInfo Schema:

schemas.contactInfo = new ObjectType({
    strict: false,
    fields: {
        firstName: new StringType({
            required: true
        }),
        lastName: new StringType({
            required: true
        }),
        birthDate: new DateType({
            required: false,
            lowerBound: "1800-01-01",
            upperBound: "2006-01-01"
        })
    }
});

Types

Any of the Facets types referenced in this section will adhere to this default set of Facets type declarations.

<!-- Default DataModel Types -->
<datamodelType type="file" class="org.tbradford.facets.datamodels.FileDataModel" />
<datamodelType type="jdbc" class="org.tbradford.facets.datamodels.JDBCDataModel" />
<datamodelType type="session" class="org.tbradford.facets.datamodels.SessionDataModel" />
<datamodelType type="xmldb" class="org.tbradford.facets.datamodels.XMLDBDataModel" />
<!-- Default Method Types -->
<methodType type="data" class="org.tbradford.facets.methods.DataMethod" />
<methodType type="dispatch" class="org.tbradford.facets.methods.DispatchMethod" />
<methodType type="javascript" class="org.tbradford.facets.methods.JavaScriptMethod" />
<methodType type="page" class="org.tbradford.facets.methods.PageMethod" />
<methodType type="redirect" class="org.tbradford.facets.methods.RedirectMethod" />
<methodType type="sequence" class="org.tbradford.facets.methods.SequenceMethod" />
<methodType type="validate" class="org.tbradford.facets.methods.ValidateMethod" />
<!-- Default Page Types -->
<pageType type="jsp" class="org.tbradford.facets.pages.WebPage" />
<pageType type="html" class="org.tbradford.facets.pages.WebPage" />
<pageType type="xml" class="org.tbradford.facets.pages.XMLPage" />
<pageType type="javascript" class="org.tbradford.facets.pages.JavaScriptPage" />

Data Model Types

Facets will declare several DataModel types. These types will not be implemented until the overall server side architecture is defined.

FileDataModel

FileDataModel encapsulates the ability to manage disk-based files both in XML and textual property list format. Multiple property lists and XML files may be registered with a single FileDataModel instance. Property lists will obviously only have a single level of associativity, though XML documents may be recursively nested. XML documents exposed via this model are managed directly, though mixed content will not be supported.

JDBCDataModel

JDBCDataModel exposes the ability to map relational data to the DataModel API via the JDBC API. This is accomplished by mapping one to many queries to various portions of the DataModel's associative tree.

SessionDataModel

SessionDataModel allows a developer to leverage information existing in the Servlet request's associated Session instance. It is capable of traversing JavaBeans that expose data via native and wrapper types, as well as Java Collections.

XMLDBDataModel

XMLDBDataModel exposes the ability to map XML data to the DataModel API using the XML:DB API for XML Database access. XML documents exposed via this model are managed directly, though mixed content will not be supported.

Method Types

Facets declares several functional and starter Method types.

DataMethod

DataMethod provides the ability to move information to and from a DataModel instance. This includes between DataModel instances and the current Servlet request's form parameters. This Method will not be implemented until the DataModel APIs are defined.

DispatchMethod

DispatchMethod dispatches method calls to Java methods declared in a subclass. These Java methods must implement the single-parameter Message signature (like Method.processMessage()). Methods of this type must be explicitly declared for each individual individual Java method that is exposed. Obviously, the DispatchMethod class must be subclassed to be useful.

Example:

<methodType type="tableManager" class="org.facets.examples.TableManager" />
...
<methods>
    <method name="addRow" type="tableManager" />
    <method name="insertRow" type="tableManager" />
    <method name="delRow" type="tableManager" />
    <method name="clear" type="tableManager" selector="clearAll" />
</methods>

You'll notice that the 'selector' attribute can be used to override the Java method that is executed when this Method is triggered. So in the case of the Facets 'clear' Method, the associate Java class' 'clearAll' method is called. Otherwise, Java methods are called with the same name as is defined in the Facets Method.

JavaScriptMethod

JavaScriptMethod allows a developer to define Method behavior using embedded JavaScript statements. This saves the trouble of having to create and manage Java Method implementation classes that do simple things that could otherwise be handled by a JavaScript interpreter. The entire Message structure is available to the JavaScript interpreter.

Example:

<method name="jsexample" type="javascript">
    var username = message.getSession().getAttribute('USER_NAME');
    if ( username == 'tom' )
        message.setAttribute('isTom', 'yes');
</method>

PageMethod

PageMethod searches the current Context and its parents for the specified Page name, and performs a redirect to that Page.

Example:

<method name="submit" type="page" page="myProfile" />

RedirectMethod

RedirectMethod performs a redirect to the specified URI. This URI may either be relative to the current Servlet context, or may be an absolute URI to an external resource.

Example:

<method name="leave" type="redirect" uri="http://www.tbradford.org/" />

SequenceMethod

SequenceMethod allows a developer to define a string of Method references that will be called in turn when this Method is triggered. Because Methods in Facets are generally free of navigable side-effects, this allows for very powerful chaining of functionality.

Example:

<method name="post" type="sequence">
    <methodReference name="prepareData" />
    <methodReference name="moveData" />
    <methodReference name="validateData" />
</method>

ValidateMethod

ValidateMethod performs validation of a DataModel, both implicitly based on the DataModel itself, and explicitly using definitions associated with the ValidateMethod itself. This method will not be implemented until the DataModel API is defined.

Page Types

Facets declares several Page types.

JavaScriptPage

JavaScriptPage allows Pages to dynamically be generated using the JavaScript language. The actual value of this Page type will not be evident until the client side architecture is fully specified.

WebPage

WebPage is the standard "go to" for HTML and JSP page serving. Pages defined using this type are served just as they would be served directly by the Servlet context itself.

XMLPage

XMLPage allows XSLT-transformed XML pages to be served. This Page type will determine whether it is necessary to perform this transformation on the client or server side, and take action as appropriate. The transformation can also be explicitly forced to be performed on the server.

Simple Facets Configuration Example

<facetsConfig>
    <types>
        <methodType type="page" class="org.tbradford.facets.methods.PageMethod" />
        <pageType type="html" class="org.tbradford.facets.pages.WebPage" />
    </types>

    <pages>
        <page name="index" type="html" src="/index.html">
            <methods>
                <method name="login" type="page" page="login" />
            </methods>

            <methodMap>
                <map scope="form" key="submit" value="login" method="login" />
            </methodMap>
        </page>

        <page name="myProfile" type="html" src="/profile.html" />

        <page name="login" type="html" src="/login.html">
            <methods>
                <method name="submit" type="page" page="myProfile" />
                <method name="cancel" type="page" page="index" />
            </methods>

            <methodMap>
                <map scope="form" key="submit" value="ok" method="submit" />
                <map scope="form" key="submit" value="cancel" method="cancel" />
            </methodMap>
        </page>
    </pages>
</facetsConfig>

Project Information

Facets is hosted at SourceForge. The following links will help you quickly find certain aspects of the project.