Generic data centric object structure and implementation6658425Abstract In an object oriented programming environment, an active data object accesses its stored data (such as its property and state data) by inlining the access code at the point at which access to the data on that property or state is required. Multiple access requests to the same data result in inlining the same access code in the data object multiple times. A system is thus provided to isolate the access code from active data objects in a program, to persistent wrapper objects that are reusable by one data object or many. It is the wrapper object that inlines all of the access code in a single location. Each data object user of the access code merely inlines a method invocation on the appropriate wrapper object. This reduces the size of data objects where there is now only a proliferation of single method invocations for most access request. It also simplifies the procedure for changing stored data, where only the references in individual access code blocks in wrapper objects need be changed, rather than at multiple access points throughout a data object. Claims The embodiments of the invention in which an exclusive property or privilege is claimed are defined as follows: Description FIELD OF THE INVENTION
string docSize( )
{
return DocumentPO.docSize( );
}
All of the code for accessing the document data, including the docSize property, is found in the PO, instead of being inlined in the DO. The code generated in a PO for "getting" a particular attribute is reusable by any user of the PO. In the same way, entering changes to the stored data is also modularized. A brief method invocation, often a single statement, in the body of the DO invokes the appropriate update methods in one or more persistent objects, in the order required for effectively updating the stored data. This is described in detail below in conjunction with FIG. 13 illustrating an aspect of the preferred embodiment. This approach enables the user to minimise the amount of code by creating PO interfaces to the data source(s) for frequently accessed views of the data. However, the basic structure of the DO remains unchanged, and direct mapping, with inlined code, or using a mapping helper class outside the builder tool, is still possible, particularly for complex attributes in the DO that require mapping that is unique to the particular DO and isolated from the reusable PO. This could include infrequently accessed document views, or views that include data requiring special approvals or security to access. The example shown in FIG. 3 illustrates a relatively complex situation. For an instance of a document, the data object (DO) 100 has at least three persistent objects (PO), a PO 102 representing the data in a document table 114 in a first data base 108, a PO 104 representing the data in a document view table 116 in a GUI (graphical user interface) data base 110, and a PO 106 representing the data in the document index table 118 in a third data base 112. When the user wants access to author information, the DO 100 will invoke "get author" on PO 106 representing document index table 112, rather than having its own copy of the access code. Similarly, to access the document body, the DO 100 will delegate, and invoke the "get body" method on PO 102 representing document table 114. In both cases, the "get" method invocation represents a single line of code in the body of the DO, rather than a copy of the data from the data base table. The invention has been implemented in an object builder tool that automates the above-described modularization of data base access code in persistent object (PO) wrappers in a data object (DO). The tool operates to generate the code to create one or more PO wrappers containing the attributes and methods of the DO. FIG. 4 is a flow diagram setting forth the steps and decisions taken in the tool to map and then generate this PO wrapper code. FIGS. 5 through 12 are a series of views or windows from a computer display of the user interface in the object builder tool. These views illustrate the manner in which the steps for setting parameters for the get and set methods for the PO's are presented to the user and information is elicited for code generation to achieve the PO wrapping. Although the parameters for the get and set methods are defined by the programmer, they are a reflection of the rules in the database. In the case of relational databases, these are strict rules. For example, referring back to FIG. 3 where the serial number in the document index table 118 is the foreign key for the document table 114, proper sequencing requires that a new serial number is indexed before it is added (set) in the document table. Particularly in the case of multiple PO's, the data object has the responsibility of dealing with how the PO's relate to each other. By isolating out the PO's, the data object deals purely with the relationship of the PO's to each other in the context of the data object. This is similar to the modularizations done when data schema are made of various configurations over a set of tables. In FIG. 4, the user must first select the PO wrapper to be used for the simple access code (block 200). The user can select as many instances of POs as needed (block 202), typically one PO instance for each database table being accessed. Each PO instance must have a unique name relative to the other PO instances, although instances of the same PO type can be multiplied, if needed. FIG. 5 illustrates a user interface in the builder tool. The illustrated window 300 for defining persistent objects associated with a specific data object, includes an instance name field 302 which is automatically defined when the user makes a selection from the list in the type field 304, and a tree view window 306, called Persistent Object Instances, that presents a current hierarchy as the persistant objects associated with the data object are built by the user. The type field list 304 contains a scrollable list of PO types that the builder tool knows about. The user can also input an override type name into field 304 that is not found in the tool's list. In that case, the builder tool will check that the user override name is unique before generating the PO. A PO is generated when the user imports a data base table definition (as in the example discussed and illustrated herein). This is supported as an import for tables, views (SQL types) or from a JavaBean. The windows 300, 310, 340 illustrated in FIGS. 5 through 12 include other common windowing features, such as size and close icons, and buttons to move forward and backward, and to end the window or access help, but as these form no part of the invention, they have not been separately referenced. Returning to FIG. 4, once the persistent object instance(s) for a data object have been specified (blocks 200, 202), each DO attribute must be associated with a PO attribute (block 204). In the builder tool view shown in FIG. 6, the Attributes Mapping window 310 (which is similar in appearance to the Associated Persistent Objects window 300 shown in FIG. 5) includes the field 312 in which a single mapped persistent object attribute is shown, field 314 defining the mapped attribute's type, and tree view 316 showing the attribute hierarchy as it is constructed. This mapping can be a one-to-one mapping, as shown in FIG. 6, or one DO attribute can be mapped to many PO attributes, as shown in FIG. 7. Consequently, the type (shown in field 314) may vary. In FIG. 6, the DO attribute name, drawn from the type field 314, is identical to the PO attribute name 312. In FIG. 7, the attribute type 312 is simply described as "long", and the attribute of "customer name" maps to two "customer" PO attributes shown in the tree view 316, "customer name" and "agent". For each attribute mapping set, the user selects the type of mapping to be done as the value moves from the DO interface to the PO interface (block 206 in FIG. 4). The tool of the preferred embodiment queries the user input to provide the appropriate mapping. Currently, three options are provided as illustrated in the screen captures of FIGS. 8 through 10. The user can select default values provided by the tool (blocks 208, 210, in FIG. 4), or can input parameters for a pattern recognised by the tool, such as map as a key or as an object reference (blocks 212, 214). If the pattern is not a recognised pattern or default mapping, the user must provide a helper class to provide definitions for the class and its methods (block 216). As shown in FIG. 8, the default mapping option 322 equates to a simple assignment statement. As shown in FIG. 9, Mapping as a Key is a type of mapping pattern completely automated within the tool of the preferred embodiment. The tool queries for further details by providing a list of the recognised patterns 326, so that the appropriate code can be inserted into the DO and PO interfaces. The provision, in the tool, of this selection list of known patterns again leads the user to a simple assignment statement. If the user does not want to map using the automatic mapping values provided by the tool, then the user selects Map using a Helper Class (button 328 in FIG. 10). The user must provide a class to do the mapping in field 330 and identify which method to use for each directional movement of the values from the DO to the PO in field 332, and from the PO to the DO in field 334. The tool will not generate inline code in the DO to do the mapping; instead, a method invocation on the specified helper class will be used for the mapping. If the user changes the mapping required between the DO and the PO attribute using a helper class, the only required change is in the mapping helper called in the DO methods. Once all of the attribute sets for each attribute in the data object are mapped (blocks 218, 220 in FIG. 4), the methods must be mapped (block 222). For the tool of the preferred embodiment, this is illustrated in the screen captures of FIGS. 11 and 12. In this tool, the methods of the DO, called "framework methods" 344, can be mapped to one or more PO method. This, in FIG. 11, the DO method insert listed in the tree in field 344 can be mapped to a single PO insert method (entry in field 342 is added to the tree in field 344). If more than one PO method is needed, then the user can simply select them. Thus, in FIG. 12, a PO delete method selected in field 342 has been added to the mapping of the DO's insert function in the tree displayed in field 344. As with the attributes, if the sequence of the accessing the methods is important, the user can specify the sequence by the order in which the PO methods are listed in the mapping tree 344, adjusting that sequence by selecting the move up/down button for the field. By providing the two sets of UI specifications to separately define attribute and method mapping, the tool can gather enough information from the user to generate all the DO and PO code involved in DO to PO mapping according to the invention. When all of the mapping is complete in the tool of the preferred embodiment, the code can be emitted. As a result of following the invention, the DO getter method could look as simple as:
string custName( )
{
return iCsCustomerPO.custName( );
}
In the case of the implementation environment of the preferred environment, CBSeries, the code would be generated as:
string custName( )
{
::CORBA::String_var iCustNameTemp;
iCustNameTemp = iCsCustomerPO.custName( );
return CORBA::string_dup(iCustNameTemp);
}
The additional code required to handle conversions to CORBA and other program model issues are inlined in the DO. Thus, these DO unique issues are handled without touching the PO. As discussed above, updating changes in the data base is accomplished by invoking an update method in the PO. A simple statement in the DO, such as:
void update( )
{
iClaim PO.update( );
}
invokes the method defined in the following code segment, an update method in the DO that has been mapped to a single PO, as illustrated in FIG. 13.
void update( )
{
// This method body is generated based on the properties set
for this PO
// To modify the content, please change the properties of this PO
EXEC SQL INCLUDE SQLCA;
EXEC SQL WHENEVER NOT FOUND GO TO sqlerror;
EXEC SQL WHENEVER SQLERROR GO TO sqlerror;
sClaimNo = &iSClaimNo;
sClaimNoIndicator = &iSClaimNoIndicator;
sExplanation = &iSExplanation;
sExplanationIndicator = &iSExplanationIndicator;
sThePolicy = &iSThePolicy;
sThePolicyIndicator = &iSThePolicyIndicator;
EXEC SQL UPDATE eleni.claim SET
claimNo = :*sClaimNo :*sClaimNoIndicator,
explanation = :*sExplanation :*sExplanationIndicator,
thePolicy = :*sThePolicy :*sThePolicyIndicator
WHERE
claimNo = :*sClaimNo :*SClaimNoIndicator;
EXEC SQL WHENEVER NOT FOUND CONTINUE; // note this
is a macro for following
EXEC SQL WHENEVER
EXEC SQL WHENEVER SQLERROR CONTINUE; // and not
inline code as with other
EXEC SQL statements
return;
sqlerror:
if (SQLCODE == 100)
throw IBOIMException::IDataKeyNotFound( );
else
{
sqlaintp(sqlMessage, 500, 70, &sqlca);
throw IBOIMException::IDataObjectFailed("Object Builder.BR2",
0, SQLCODE, "claimPO.update", sqlMessage);
}
}
The above code for the update method in the PO is all that is generated and can be reused by any user of the PO. A more complex mapping of one DO attribute to different PO attributes simply adds one line to the DO update method. If the same PO is being used, no additional PO code is required. While the present invention may be implemented in any object oriented programming environment, it is particularly useful in an interpreted environment such as Java or Smalltalk. Productive use of the invention in a compiled OO environment such as C++can be made, provided (specifically in the case of C++) that indirect references are minimised. Modifications of the invention that would be obvious to the person skilled in the art are intended to be covered by the scope of the appended claims.
APPENDIX
Using the tool of the preferred embodiment,
the following complete code sample of a
persistent object was generated for the
"customer" described above.
EXEC SQL INCLUDE SQLDA;
//-------------------------------------------------------------------------
csCustomerPOKey::csCustomerPOKey( )
{
iSCustomerNoIndicator = NULL_PO_VALUE;
}
cSCustomerPOkey::.about.csCustomerPOKey( )
{ }
//-------------------------------------------------------------------------
// the get/set methods of the PO csCustomerPOKey
//-------------------------------------------------------------------------
// get customerNo
long csCustomerPOKey::customerNo( )
{
if (iSCustomerNoIndicator == NULL_PO_VALUE)
iSCustomerNo = 0;
return iSCustomerNo;
}
// set customerNo
void csCustomerPOKey::customerNo(long aCustomerNo)
{
iSCustomerNo = aCustomerNo;
iSCustomerNoIndicator = NON_NULL_PO_VALUE;
}
//-------------------------------------------------------------------------
csCustomerPOCopy::csCustomerPOCopy( )
{
iSCustNameIndicator = NULL_PO_VALUE;
iSSalesIndicator = NULL_PO_VALUE;
iSCustomerNoIndicator = NULL_PO_VALUE;
iSCustomerSsNoIndicator = NULL_PO_VALUE;
iSAgentIndicator = NULL_PO_VALUE;
}
csCustomerPoCopy::.about.csCustomerPOCopy( )
{ }
//-------------------------------------------------------------------------
// the get/set methods of the PO csCustomerPOCopy
//-------------------------------------------------------------------------
// get custName
char* csCustomerPOCopy::custName( )
{
if (iSCustNameIndicator == NULL_PO_VALUE)
return NULL;
else
return CORBA::string_dup(iSCustName);
}
// set custName
void csCustomerPOCopy::custName(char* aCustName)
{
if (aCustName == NULL)
iSCustNameIndicator = NULL_PO_VALUE;
else {
memcpy(iSCustName, aCustName, 101);
iSCustNameIndicator = NON_NULL_PO_VALUE;
}
}
// get sales
double csCustomerPOCopy::sales( )
{
if (iSSalesIndicator == NULL_PO_VALUE)
iSSales = 0;
return iSSales;
}
// set sales
void csCustomerPOCopy::sales(double aSales)
{
iSSales = aSales;
iSSalesIndicator = NON_NULL_PO_VALUE;
}
// get customerNo
long csCustomerPOCopy::customerNo( )
{
if (iSCustomerNoIndicator == NULL_PO_VALUE)
iSCustomerNo = 0;
return iSCustomerNo;
}
// set customerNo
void csCustomerPOCopy::customerNo(long aCustomerNo)
{
iSCustomerNo = aCustomerNo;
iSCustomerNoIndicator = NON_NULL_PO_VALUE;
}
// get customerSsNo
char* csCustomerPOCopy::customerSsNo( )
{
if (iSCustomerSsNoIndicator == NULL_PO_VALUE)
return NULL;
else
return CORBA::string_dup(iSCustomerSsNo);
}
// set customerSsNo
void csCustomerPOCopy::customerSsNo(char* aCustomerSsNo)
{
if (aCustomerSsNo == NULL)
iSCustomerSsNoIndicator = NULL_PO_VALUE;
else {
memcpy(iSCustomerSsNo, aCustomerSsNo, 101);
iSCustomerSsNoIndicator = NON_NULL_PO_VALUE;
}
}
// get agent
long csCustomerPOCopy::agent( )
{
if (iSAgentIndicator == NULL_PO_VALUE)
iSAgent = 0;
return iSAgent;
}
// set agent
void csCustomerPOCopy::agent(long aAgent)
{
iSAgent = aAgent;
iSAgentIndicator = NON_NULL_PO_VALUE;
}
csCustomerPO::csCustomerPO( )
{
iSCustNameIndicator = NULL_PO_VALUE;
iSSalesIndicator = NULL_PO_VALUE;
iSCustomerNoIndicator = NULL_PO_VALUE;
iSCustomerSsNoIndicator = NULL_PO_VALUE;
iSAgentIndicator = NULL_PO_VALUE;
}
csCustomerPO::.about.csCustomerPO( )
{ }
//-------------------------------------------------------------------------
// the get/set methods of the PO csCustomerPO
//-------------------------------------------------------------------------
// get custName
char* csCustomerPO::custName( )
{
// This method body is generated based on the properties set for this PO
if (iSCustNameIndicator == NULL_PO_VALUE)
return NULL;
else
return CORBA::string_dup(iSCustName);
}
// set custName
void csCustomerPO::custName(char* aCustName)
{
// This method body is generated based on the properties set for this PO
if (aCustName == NULL)
iSCustNameIndicator = NULL_PO_VALUE;
else {
memcpy(iSCustName, aCustName, 101);
iSCustNameIndicator = NON_NULL_PO_VALUE;
}}
// get sales
double csCustomerPO::sales( )
{
// This method body is generated based on the properties set for this PO
if (iSSalesIndicator == NULL_PO_VALUE)
iSSales = 0;
return iSSales;}
// set sales
void csCustomerPO::sales(double aSales)
{
// This method body is generated based on the properties set for this PO
iSSales = aSales;
iSSalesIndicator = NON_NULL_PO_VALUE;}
// get customerNo
long csCustomerPO::customerNo( )
{
// This method body is generated based on the properties set for this PO
if (iSCustomerNoIndicator == NULL_PO_VALUE)
iSCustomerNo = 0;
return iSCustomerNo;}
// set customerNo
void csCustomerPO::customerNo(long aCustomerNo)
{
// This method body is generated based on the properties set for this PO
iSCustomerNo = aCustomerNo;
iSCustomerNoIndicator = NON_NULL_PO_VALUE;}
// get customerSsNo
char csCustomerPO::customerSsNo( )
{
// This method body is generated based on the properties set for this PO
if (iSCustomerSsNoIndicator == NULL_PO_VALUE)
return NULL;
else
return CORBA::string_dup(iSCustomerSsNo);}
// set customerSsNo
void csCustomerPO::customerSsNo(char* aCustomerSsNo)
{
// This method body is generated based on the properties set for this PO
if (aCustomerSsNo == NULL)
iSCustomerSsNoIndicator = NULL_PO_VALUE;
else {
memcpy(iSCustomerSsNo, aCustomerSsNo, 101);
iSCustomerSsNoIndicator = NON_NULL_PO_VALUE;
}}
// get agent
long csCustomerPO::agent( )
{
// This method body is generated based on the properties set for this PO
if (iSAgentIndicator == NULL_PO_VALUE)
iSAgent = 0;
return iSAgent;}
// set agent
void csCustomerPO::agent(long aAgent)
{
// This method body is generated based on the properties set for this PO
iSAgent = aAgent;
iSAgentIndicator = NON_NULL_PO_VALUE;}
//-------------------------------------------------------------------------
// the framework level methods of the PO csCustomerPO
//-------------------------------------------------------------------------
//
// insert method
//
void csCustomerPO::insert( )
{
// This method body is generated based on the properties set for this PO
// To modify the content, please change the properties of this PO
EXEC SQL BEGIN DECLARE SECTION;
char (*sCustName)[101];
short* sCustNameIndicator;
double* sSales;
short* sSalesIndicator;
long* sCustomerNo;
short* sCustomerNoIndicator;
char (*sCustomerSsNo)[101];
short* sCustomerSsNoIndicator;
long* sAgent;
short* sAgentIndicator;
EXEC SQL END DECLARE SECTION;
EXEC SQL INCLUDE SQLCA;
EXEC SQL WHENEVER NOT FOUND GO TO sqlerror;
EXEC SQL WHENEVER SQLERROR GO TO sqlerror;
sCustName = &iSCustName;
sCustNameIndicator = &iSCustNameIndicator;
sSates = &iSSales;
sSalesIndicator = &iSSalesIndicator;
sCustomerNo = &iSCustomerNo;
sCustomerNoIndicator = &iSCustomerNoIndicator;
sCustomerSsNo = &iSCustomerSsNo;
sCustomerSsNoIndicator = &iSCustomerSsNoIndicator;
sAgent = &iSAgent;
sAgentIndicator = &iSAgentIndicator;
EXEC SQL INSERT INTO csCustomer
(custName, sales, customerNo, customerSsNo, agent)
VALUES
( :*sCustName :*sCustNameIndicator, :*sSales :*sSalesIndicator,
:*sCustomerNo
:*sCustomerNoIndicator, :*sCustomerSsNo :*sCustomerSsNoIndicator, :*sAgent
:*sAgentIndicator);
EXEC SQL WHENEVER NOT FOUND CONTINUE; // note this is a macro for
following EXEC SQL
WHENEVER
EXEC SQL WHENEVER SQLERROR CONTINUE; // and not inline code as with other
EXEC SQL
statements
return;
sqlerror:
if (SQLCODE == 803)
throw IBOIMException::IDataKeyAlreadyExists( );
else
{
char sqlMessage[500];
sqlaintp(sqlMessage, 500, 70, &sqlca);
throw IBOIMException::IDataObjectFailed("Object Builder", 0, SQLCODE,
"csCustomerPO.insert",
sqlMessage);
}
}
//
// retrieve method
//
void csCustomerPO::retrieve( )
{
// This method body is generated based on the properties set for this PO
// To modify the content, please change the properties of this PO
char (*sCustName)[101];
short* sCustNameIndicator;
double* sSales;
short* sSalesIndicator;
long* sCustomerNo;
short* sCustomerNoIndicator;
char (*sCustomerSsNo)[101];
short* sCustomerSsNoIndicator;
long* sAgent;
short* sAgentIndicator;
EXEC SQL INCLUDE SQLCA;
EXEC SQL WHENEVER NOT FOUND GO TO sqlerror;
EXEC SQL WHENEVER SQLERROR GO TO sqlerror;
sCustName = &iSCustName;
sCustNameIndicator = &iSCustNameIndicator;
sSales = &iSSales;
sSalesIndicator = &iSSalesIndicator;
sCustomerNo = &iSCustomerNo;
sCustomerNoIndicator = &iSCustomerNoIndicator;
sCustomerSsNo = &iSCustomerSsNo;
sCustomerSsNoIndicator = &iSCustomerSsNoIndicator;
sAgent = &iSAgent;
sAgentIndicator = &iSAgentIndicator;
EXEC SQL SELECT
custName, sales, customerNo, customerSsNo, agent
INTO
:*sCustName :*sCustNameIndicator, :*sSales :*sSalesIndicator,
:*sCustomerNo
:*sCustomerNoIndicator, :*sCustomerSsNo :*sCustomerSsNoIndicator, :*sAgent
:*sAgentIndicator
|
Same subclass Same class Consider this |
||||||||||
