Consultor Eletrônico



Kbase 20254: ADM2. SmartDataObjects Explained, or Reasons to Use SDOs
Autor   Progress Software Corporation - Progress
Acesso   Público
Publicação   21/06/2006
Status: Verified

GOAL:

What is a SmartDataObject (SDO), types of SDOs, and how they work?

GOAL:

How do Smart Data Object work?

GOAL:

What is a Smart Data Object?

GOAL:

Types of Smart Data Objects.

FACT(s) (Environment):

Progress 9.x
OpenEdge 10.x

FIX:

The release of Progress Version 9.0A software introduces a new powerful feature, SmartDataObjects.
A SmartDataObject is an ADM data-class SmartObject that defines a set of records to fetch from the database. SDOs coordinate with other SmartObjects such as SmartDataViewers (SDV) and SmartDataBrowsers (SDB) to manage records in an application. SDOs respond to navigation controls such as those available in SmartPanels and SmartToolBars.

The following information is based on the assumption that you are developing an application with the following characteristics:

- Uses Progress 9.

- The application must be able to be deployed in either a client/server or a distributed environment

- Uses ADM2

- The application is thin client

In addition, this solution takes the approach of using one SmartDataObject per table in the database, with the goal of encapsulating all relevant functionality in a single place.

The information is presented in the following six sections:

1) SmartObjects in a distributed environment:

When a new SDO is created, the AppBuilder generates three source files:

- The SDO source (sdo.w)

- A client proxy (sdo_cl.w)

- An include file detailing the RowObject temporary table (sdo.i)

Two r-code files are created from these source files, one for the main SDO (sdo.r) and one for the client proxy (sdo_cl.r).

In a traditional client/server environment, the significance of these files is unimportant. You deploy all r-code as usual.
However, in a distributed environment, it is important to know the difference between the client proxy code and the full SDO.

The client proxy code is always smaller.

Remember that this example assumes that the developer is striving for a thin client (for example, all database access occurs on the AppServer).
Therefore, the explanation for the smaller size of the client proxy is that it contains only the code that is relevant to the client. At compile time, only those procedures that do not access the database are compiled into this r-code.
(Any user-defined procedures and functions that are marked as 'database required' are excluded.)

The two pieces of r-code above that relate to the same SDO are used in the following way:

- In a client/server environment, only the full SDO (sdo.r) is deployed.

- In a distributed environment, you should deploy the client proxy file (sdo_cl.r) on the client and the full SDO (sdo.r) on the AppServer.

2) SmartDataObject initialization:

From this point on, assume you have a distributed, thin client environment.

As noted above, there are two versions of each SDO, one that can function with a database attached and another that requires no database. Both versions must be initiated at runtime.

This is achieved during the initialization of the client proxy. As the client proxy object initializes, it recognizes that an AppServer is present and proceeds to create and initialize the full SDO on the AppServer.

You now have two distinct objects running in two completely separate sessions. As long as you stay within the narrow confines of the standard ADM2 procedures and functions, the interaction of these two objects is completely transparent. However, once additional user-defined procedures are introduced to the SDO, you
must ensure that data is passed correctly between the client and server objects.

The remainder of this Knowledge Base solution presents solutions to some of the problems that can arise.

3) Useful procedures and handles:

- To find out which object is currently running:

The call:

{get ASDivision cASDivision}.

returns one of the following results:

- Client

&nbsp.; The procedure is running in the client proxy.

- Server

The procedure is running in the full SDO on the AppServer.

- 'Blank'

The procedure is running in the full SDO in a client/server environment.

- To find the handle of the server side SDO:

{get ASHandle hASHandle}

This call returns the handle to the full SDO running on the AppServer.

4) Modification of the WHERE clause during initialization:

One result of the decision in this example explanation to have one SDO per table is that the query that is associated with an SDO is always defined such that all records in the primary table are retrieved. This is not a particularly useful feature.

However, in theory it is not difficult to change the WHERE clause of the SDO during initialisation so that only the desired records are retrieved. (For example, at runtime an SDO for the UserCompany table might be required to show only those records for the currently selected user.)

In a distributed environment, changing the WHERE clause is complicated by the initialization procedure for the two versions of the SDO:

- The WHERE clause of the client is redundant because it is the AppServer version of the SDO that retrieves the record. However, it is the client that must initiate any openQuery() requests.

- The AppServer SDO is created during the initialization of the client proxy, therefore no context information can be passed to the server side until the client proxy has finished initializing.

Therefore, the solution becomes:

After the standard procedure, call in the client initializeObject procedure and set the WHERE clause in the server object and call the OpenQuery() procedure in the client object.

This solution is illustrated in the following code. The code fragment contains another implicit assumption; that the context information for the user session is being held on the client side.
This means that the client must pass any relevant context information to the server side in order to achieve the desired results. This can be seen in the call to the 'setQueryWhere' function in the server side object (hASHandle):

PROCEDURE initializeObject:

DEFINE VARIABLE cUser AS CHARACTER NO-UNDO.
DEFINE VARIABLE cQuery AS CHARACTER NO-UNDO.
DEFINE VARIABLE cASDivision AS CHARACTER NO-UNDO.
DEFINE VARIABLE hASHandle AS HANDLE NO-UNDO.

RUN SUPER.

{get ASDivision cASDivision}.

IF cASDivision <> 'server':U THEN
DO:
{get ASHandle hASHandle}.
...
... Get the current user from wherever it
is stored
and
... assign it to cUser.
...
ASSIGN cQuery=SUBSTITUTE('mnUserId=&1':U,
&.nbsp; cuser).
END.

IF cASDivision='client':U THEN /* Distributed -
Client Side */
DO:
DYNAMIC-FUNCTION('setQueryWhere':U IN hASHandle,
INPUT cQuery).
openQuery().
END.
ELSE
IF cASDivision <> 'server':U THEN /* Client Server */
DO:
DYNAMIC-FUNCTION('setQueryWhere':U, INPUT cQuery).
openQuery().
END.

END PROCEDURE.
5) User defined functions:

As previously noted, the client proxy version of the SDO does not include any procedure that requires database access. So what happens if you where to add a procedure to an SDO that deals with a user table and that verifies a UserID and password as part of a login process?

The procedure must accept the UserID and password as parameters and perform a database lookup to verify the user (for example, it would require database access and thus would not be compiled into the client proxy.)

If the SDO was deployed in this state, Progress will complain at run time that it could not find the required procedure.

The solution to this problem is to uncheck the database required flag against the procedure and use the preprocessors:

{&DB-REQUIRED-START}

and

{&DB-REQUIRED-END}

These preprocessor statements tell the compiler which segments of code to exclude from the client proxy r-code.

Notice in the following code that in addition to excluding the database requiring code from the client proxy, it is also necessary to make a call to the server side object in order to execute the code:

PROCEDURE verifyUser:

DEFINE INPUT PARAMETER pcUserName AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER pcPassWord AS CHARACTER NO-UNDO.
DEFINE OUTPUT PARAMETER piUserId AS INTEGER NO-UNDO.

/*
** If this procedure is running on the client side of the 'wall'
** invoke the same procedure on the server side (because that's where
** the d/b access stuff is). */

IF DYNAMIC-FUNCTION('getASDivision':U)="Client":U THEN
RUN verifyUser IN DYNAMIC-FUNCTION('getASHandle':U)
(INPUT pcUserName,
INPUT pcPassWord,
&.nbsp; OUTPUT piUserId).

{&DB-REQUIRED-START}

FIND FIRST mnUser
WHERE mnUser.UserName=pcUserName
AND mnUser.Password=ENCODE(pcPassword)
NO-LOCK NO-ERROR.

ASSIGN piUserId=IF AVAILABLE mnUser THEN mnUser.mnUserId
ELSE ?.

{&DB-REQUIRED-END}

END PROCEDURE.6) The SDO update transaction:

One of the biggest omissions from the Version 9.0B ADM2 is the ability to call user defined routines from within the update transaction.

Although you can call your own procedures before the transaction by using the hook 'transactionValidate', the procedure serverCommit (in data.i), any database changes are outside the scope of the primary transaction and thus will not be undone if the primary transaction fails for any reason.

To workaround this problem, follow these steps:

1) Take a copy of data.i.

2) Place the copy before the original in the development version ProPath.

3) Amend the procedure serverCommit to include the procedure calls 'beginTransactionValidate' and TransactionValidate' at the start and end of the main transaction respectively.

Now you can include these procedures within SDOs and gain the required 'extra' functionality.
.