3D PLM Enterprise Architecture

User Interface - CATJDialog

Writing Stateless Controllers

Best practices to design stateless controllers
Technical Article

Abstract

This article gives you several rules and best practices to help you designing stateless JDialog controllers. This is a key topic if you want your application to scale.


General thoughts about scalability

In an application server environment, there is no choice between memory or CPU consumption. The critical resource in such an environment is definitely memory. Only applications with low memory usage (per user session) will scale. Obviously CPU consumption is the price to pay for a low memory usage.
The application has then to try to be "as stateless as possible".

For example, an application that displays a query result in a table should not hold the result data between two client requests. Indeed, what would happen if the user never connects again after the first request? The server would have to wait until the session timeout to release the result data (that may be huge!).
Therefore - even if it's CPU consuming - the application has to (re) make the query at each client request, and drop the result at the end of the request.

Some definitions:

[Top]

Is JDialog stateless?

JDialog provides the application with a collection of graphical components (widgets).
Those widgets are aimed at showing application data to the user.
From the widget point of view, we call it presentation data.

For example, for a TextField, the presentation data is the displayed text. For a DateEditor, the presentation data is the displayed date. For a Tree, the presentation data are the tree nodes...

Most of JDialog widgets hold the presentation data:

So - as a matter of fact - JDialog is not stateless (because it holds data throughout the session).
Anyway, holding those data is not a major concern as their size is "reasonable" and well controlled.

But there are some widgets that display an uncontrolled amount of data (that may be huge):

Those widgets have a specific design not to hold the presentation data:

[Top]

Data Model: a JDialog pattern

When using a tree or a table widget, the application has to provide the widget with a data model object, that is in charge of feeding the widget with presentation data.
Those models MUST be stateless (otherwise it may hold a huge amount of data).

This design allows JDialog to request presentation data only when rendering the tree or table widget to the Graphical User Interface (GUI), and to forget it right after that.

For historical reasons and for addressing different needs, the tree and table widgets allow several types of models:

Here are allowed model types for the Table widget:

Allowed models for Table Stateless Reserved methods on CATTable
CATTableModel NO
  • setModel(), getModel()
  • setSelectedRow(), getSelectedRow(), isRowSelected()
  • getAllRowSelected()
CATKeyTableModel YES
  • setKeyModel(), getKeyModel()
  • setSelection(), getSelection()
  • setSelectedKeys(), getSelectedKeys(), isKeySelected()

Here are allowed model types for the Tree widget:

Allowed models for Tree Stateless Reserved methods on CATTree
CATTreeModel NO
  • setModel(), getModel()
  • collapse(), expand(), isExpanded()
  • setSelection(), getSelection()
  • setSelectedNodes(), getSelectedNodes()
CATKeyTreeModel YES
  • setKeyModel(), getKeyModel()
  • collapseKey(), expandKey(), isKeyExpanded()
  • selectKey(), unselectKey(), isKeySelected()
  • setSelectedKeys(), getSelectedKeys()
CATKeyPathTreeModel YES
  • setKeyPathModel(), getKeyPathModel()
  • collapseKeyPath(), expandKeyPath(), isKeyPathExpanded()
  • selectKeyPath(), unselectKeyPath(), isKeyPathSelected()
  • setSelectedKeyPaths(), getSelectedKeyPaths()

As you see in the previous charts, CATTreeModel and CATTableModel are not stateless compliant. Therefore you should not use them if you wish to write a stateless application. In the next chapter, we'll have a deep sight into CATKeyTableModel, CATKeyTreeModel and CATKeyPathTreeModel usage.

[Top]

The Key: a common language between JDialog and the application

The allowed stateless models are all key models: they identify widget items with a key (that is a string identifier).

Note: the tree 'item' is a node, the table 'item' is a row.

[Top]

How does it work?

  1. at render time, the model is asked for presentation data + keys

  2. when an event occurs on the GUI, the source item (s) is (are) identified with its (their) key (s)

Ex: a Tree with a CATKeyTreeModel with a controller listening to selection notifications:

At render time, the model is asked for each visible tree node:

  • its key (a string that identifies the application data represented by this node)

  • the node label

  • the node icon

  • the node type: node (has children) or leaf (no child)

  • the node children (if any)

Later on, the user selects a node:

  • the application receives a SelectionNotification from the Tree widget, with the key identifying the source node.

[Top]

How to choose the key?

Basically, the key should contain enough data for the application to identify clearly the related data.

Ex: a Table that shows a database query result

If a row presented in the table corresponds to a database n-uplet, then the key should obviously be the database table key.

If a table row corresponds to a combination of database n-uplets, then the key should be a combination (concatenation) of the required database tables keys.

[Top]

What is the difference between CATKeyTreeModel and CATKeyPathTreeModel?

When using a CATKeyPathTreeModel, tree nodes are no longer identified by the node key, but by the keys path from the root to the concerned node.

A key path is of type String[] and has the following format:

{<root node key>, <first children key>, [...], <the concerned node key>}

There are two cases where the application should use a CATKeyPathTreeModel instead of CATKeyTreeModel:

  1. when 2 or more tree nodes may represent the same application data

  2. when an event on a tree node has an impact on its parent nodes, and the application has no way of retrieving the parents keys programmatically (bad database design?)

Notice that identifying nodes with their keys path is heavier for the client and JDialog renderer, so applications should really be careful of using a CATKeyPathTreeModel only when needed.

[Top]

Designing a stateless KeyModel

A stateless model should retrieve its application data only once per client request, and should not keep it beyond the request lifetime. There are two cases:

Request caching is performed through the session volatile properties:

CATSession.getVolatileProperty(String iName);
CATSession.setVolatileProperty(String iName, Object iData);

Volatile properties is a memory space related to the client request. It has two advantages on other ways of caching transient data:

[Top]

Tree use case

JDialog requests 2 kinds of information when rendering a tree:

Notes:

Let's imagine we want to write a JDialog tree that presents data from a database:

Here is an implementation of this use case:

package com.dassault_systemes.myapplication;
import com.dassault_systemes.catjdialog.CATKeyTreeModel;
import com.dassault_systemes.catjdialog.CATKeyTreeModelCtxMenuEx;
import com.dassault_systemes.catjdialog.CATMenuModel;
import com.dassault_systemes.catjdialog.CATTreeNodeInfo;
public class MyStatelessKeyTreeModel implements CATKeyTreeModel, CATKeyTreeModelCtxMenuEx
{
	private String _rootKey;
	/**
	 * MyStatelessKeyTreeModel constructor
	 */
	public MyStatelessKeyTreeModel(CATSession iSession)
	{
		// --- compute the root Key for the current user
		// [ TODO ]
	}
	/**
	 * @see CATKeyTreeModelCtxMenuEx.getContextualMenu()
	 */
	public CATMenuModel getContextualMenu( String iKey )
	{
		// --- retieve contextual menu model from the application data
		// --- this info is only requested once per client request, so caching is not necessary
		// [ TODO ]
		return null;
	}
	/**
	 * @see CATKeyTreeModel.getRootKey()
	 */
	public String getRootKey()
	{
		// --- root key has already been computed
		return _rootKey;
	}
	/**
	 * @see CATKeyTreeModel.getNodeInfo()
	 */
	public CATTreeNodeInfo getNodeInfo(String iKey, boolean iGetChildren)
	{
		// --- retieve node info from the application data
		// --- this info is only requested once per client request, so caching is not necessary
		// [ TODO ]
		return null;
	}
} 

Notes:

[Top]

Table use case

JDialog requests 3 kinds of information when rendering a table:

Notes:

Unlike in the tree models, cell information are requested through several methods (getCell(), getType(), getIcon(), ...). In that case, application data should be queried once and cached in the session volatile properties.

Let's imagine we want to write a JDialog table that presents data from a database:

Here is an implementation of this use case:

package com.dassault_systemes.myapplication;

import com.dassault_systemes.catjdialog.CATDialog;
import com.dassault_systemes.catjdialog.CATKeyTableModel;

/**
 * This table model represents a database table:
 *  - displayed columns are customizable and set in the database
 *  - cell information (label, type, icon, ...) is stored in the database
 */
public class MyStatelessKeyTableModel extends CATKeyTableModel
{
	private CATDialog _dialog;
	
	/**
	 * StatelessKeyTreeModel constructor
	 * This model type needs the related dialog component (a tree)
	 */
	public MyStatelessKeyTableModel (CATDialog iDialog)
	{
		_dialog = iDialog;
	}
	// =====================================================================================
	// === COLUMN INFORMATION
	// =====================================================================================
	/**
	 * This method returns column related data and manages caching through Session Volatile Properties
	 */
	private MyColumnsData getColumnsData(CATDialog iDialog)
	{
		// --- check whether the column data is cached or not
		MyColumnsData data = (MyColumnsData)iDialog.getSession().getVolatileProperty(iDialog.getPath()+"&Column");
		
		if(data != null)
			return data;
		
		// --- retrieve columns data
		// [ TODO ]
		
		// --- cache the data for the request lifetime
		iDialog.getSession().setVolatileProperty(iDialog.getPath()+"&Column", data);
		return data;
	}
	/**
	 * @see CATKeyTableModel.getColumnCount()
	 */
	public int getColumnCount()
	{
		MyColumnsData colData = getColumnsData(_dialog);
		// --- return column count from colData
		// [ TODO ]
	}
	/**
	 * @see CATKeyTableModel.getColumnTitle()
	 */
	public String getColumnTitle(int iColumn)
	{
		MyColumnsData colData = getColumnsData(_dialog);
		// --- return column title from colData
		// [ TODO ]
	}
	/**
	 * @see CATKeyTableModel.isColumnSortable()
	 */
	public boolean isColumnSortable(int iColumn)
	{
		MyColumnsData colData = getColumnsData(_dialog);
		// --- return whether the column is sortable or not from colData
		// [ TODO ]
	}
	// =====================================================================================
	// === ROW COUNT INFORMATION
	// =====================================================================================
	/**
	 * @see CATKeyTableModel.getKeyCount()
	 */
	public int getKeyCount()
	{
		/*
		 * Two choices:
		 * ------------
		 * 1- make a complete query in database and only render the rows in the table display range
		 * 2- make a first query for counting total number of results,
		 *    and then make another query (with results) when asked for
		 * 
		 * The second choice is probably optimal.
		 * Let's imagine we are in the second choice.
		 */
		
		// --- check whether the column data is cached or not
		Integer count = (Integer)_dialog.getSession().getVolatileProperty(_dialog.getPath()+"&Count");
		
		if(count != null)
			return count.intValue();
		
		// --- make the 'count' request
		// [ TODO ]
		
		// --- cache the data for the request lifetime
		_dialog.getSession().setVolatileProperty(_dialog.getPath()+"&Count", count);
		return count.intValue();
	}
	// =====================================================================================
	// === CELL INFORMATION
	// =====================================================================================
	/**
	 * This method returns rows related from Session Volatile Properties
	 * (doesn't check whether is is valuated or not)
	 */
	private MyRowsData getRowsDataFromCache(CATDialog iDialog)
	{
		// --- check whether the column data is cached or not
		return (MyRowsData)iDialog.getSession().getVolatileProperty(iDialog.getPath()+"&Rows");
	}
	/**
	 * @see CATKeyTableModel.getKeys()
	 */
	public void getKeys( int iOffset, String [] oKeys )
	{
		/*
		 * This is the first called method before retrieving row informations
		 * It manages the database query
		 * It is only called once per client request
		 */
		// --- make the query, and keep it in a MyRowData object
		// [ TODO ]
		
		// --- cache the data for the request lifetime
		_dialog.getSession().setVolatileProperty(_dialog.getPath()+"&Rows", rowsData);
 		
 		// --- return keys from rowsData
		// [ TODO ]
	}
	/**
	 * @see CATKeyTableModel.getType()
	 */
	public int getType(String iKey, int iColumn)
	{
		MyRowsData rowsData = getRowsDataFromCache(_dialog);
		// --- (rowsData is valuated as getKeys() was called before)
		// --- return cell type from rowsData
		// [ TODO ]
	}
	/**
	 * @see CATKeyTableModel.getCell()
	 */
	public String getCell(String iKey, int iColumn)
	{
		MyRowsData rowsData = getRowsDataFromCache(_dialog);
		// --- (rowsData is valuated as getKeys() was called before)
		// --- return cell label from rowsData
		// [ TODO ]
	}
	/**
	 * @see CATKeyTableModel.getState()
	 */
	public boolean getState(String iKey, int iColumn)
	{
		MyRowsData rowsData = getRowsDataFromCache(_dialog);
		// --- (rowsData is valuated as getKeys() was called before)
		// --- return cell state from rowsData
		// [ TODO ]
	}
	/**
	 * @see CATKeyTableModel.getImage()
	 */
	public String getImage(String iKey, int iColumn)
	{
		MyRowsData rowsData = getRowsDataFromCache(_dialog);
		// --- (rowsData is valuated as getKeys() was called before)
		// --- return cell icon from rowsData
		// [ TODO ]
	}
	/**
	 * @see CATKeyTableModel.getLink()
	 */
	public String getLink(String iKey, int iColumn)
	{
		MyRowsData rowsData = getRowsDataFromCache(_dialog);
		// --- (rowsData is valuated as getKeys() was called before)
		// --- return cell link from rowsData
		// [ TODO ]
	}
}

Notes:

Warning 1: Even request caching the row count may be unsafe as the count request and the cells information request are probably performed with 2 distinct requests and that data may have changed in the meantime. So be aware that row count may have changed when you request cells information.

Warning 2: Prefixing volatile properties name with the widget's path forces the model to hold a reference to the widget (given by the controller in the model constructor). That's a problem with CATKeyTableModel design: the widget object should be passed to the model in each method (actually this is done in the new CATTreeKeyPathModel).

[Top]


History

Version: 1 [Jul 2002] Document created
[Top]

Copyright © 2000, Dassault Systèmes. All rights reserved.