After users have selected the source nodes in the Select Nodes dialog box (Step 3), the ODA is ready to begin content generation. Business Object Wizard calls the generateBoDefs() content-generation method to generate business object definitions for the user-selected source nodes. To the ODA, Business Object Wizard sends the list of source nodes (selected in Step 3). The goal of the business-object-definition generation process is to create a business object definition for each selected source node. While the generateBoDefs() method runs, Business Object Wizard displays its Generating Business Objects screen (Step 5).
This section describes the following steps that the generateBoDefs() method should take to generate business object definitions:
To provide generation of business object definitions, your ODA class (derived from ODKAgentBase2) must implement the generateBoDefs() method, is defined in the IGeneratesBoDefs interface. The generateBoDefs() method receives these user-selected source nodes as an argument, an array of source-node paths (String objects). The method must generate a business object definition for each source node in this array. It can use its path to locate the source node in the data source. As its last step, generateBoDefs() returns a content-metadata (ContentMetaData) object to describe the business object definitions it has generated.
The sample Roman Army ODA supports the on-request content protocol for the generation of business object definitions (see Figure 58). The implementation of this method generateBoDefs() in the ArmyAgent3 class includes the code fragment in Figure 63, which declares the variable for the generated-content structure (m_generatedBOs) and defines the generateBoDefs() method itself.
Figure 63. Defining the generateBoDefs() method
final Vector m_generatedBOs = new Vector();
public ContentMetaData generateBoDefs(String[] nodes) throws ODKException {
If, during the content-generation process, the ODA requires additional information, it can display the BO Properties dialog box and request values for business-object properties. For an introduction to business-object properties, see Obtaining business-object properties.
Figure 64 illustrates a sample BO Properties dialog box that displays two business-object properties:
Figure 64.
Additional property information needed.
To provide the user with the properties illustrated in Figure 64, the generateBoDefs() method takes the following steps:
The getBOSpecificProps() method requires an array of agent-property objects as an argument. This argument is the business-object-property array and contains one agent-property object for each business-object property to display in the BO Properties dialog box. Before generateBoDefs() calls getBOSpecificProps(), it must take the necessary steps to create the array that defines the business-object properties, initialize the business-object properties, and save these properties into the array.
The first step is to define a business-object-property array to hold the Verbs and Prefix properties. The next step is to initialize the business-object properties, using the AgentProperty() constructor. With this constructor, you specify values for the various metadata that the AgentProperty class supports. The AgentProperty class provides support for the business-object property to have the following features:
To initialize the Verbs and Prefix properties, you provide the AgentProperty() constructor with the following information:
Therefore, this property requires the following metadata in the AgentProperty() constructor:
Before the call to the AgentProperty() constructor, the code in Figure 65 first creates and initializes the validValues and defaultValues arrays so they are available for the constructor.
Therefore, this property requires the following metadata in the AgentProperty() constructor:
The code fragment in Figure 65 creates and initializes the business-object-properties array:
Figure 65. Creating the business-object array
// Create the business-object-property array AgentProperty AgtProps[] = new AgentProperty[2];
// Provide list of valid values for Verbs property Object[] validValues = new Object[4]; validValues[0] = new String("Create"); validValues[1] = new String("Retrieve"); validValues[2] = new String("Delete"); validValues[3] = new String("Update");
// Provide list of default values for Verbs property Object[] defaultValues = new Object[4]; defaultValues[0] = new String("Create"); defaultValues[1] = new String("Retrieve"); defaultValues[2] = new String("Delete"); defaultValues[3] = new String("Update");
// Instantiate the Verbs property AgtProps[0] = new AgentProperty("Verbs", AgentProperty.TYPE_STRING, "Verbs that are applicable to all the selected objects", false, true, ODKConstant.MULTIPLE_CARD, validValues, defaultValues);
// Instantiate the Prefix property AgtProps[1] = new AgentProperty("Prefix", AgentProperty.TYPE_STRING, "Prefix that should be applied to each business object name", false, false, ODKConstant.SINGLE_CARD, null, null);
For more information on the metadata of business-object properties, see Working with agent properties.
Once the business-object-property array is initialized, the ODA can call the getBOSpecificProps() method to pass this array to Business Object Wizard for display to the user in the BO Properties dialog box. This method is defined in the ODKUtility class, and therefore must access an ODKUtility object. Usually, you instantiate this object as part of the ODA initialization. For more information, see Obtaining the handle to the ODKUtility object.
The call to getBOSpecificProps() in Figure 66 sends the AgtProps array (initialized in Figure 65) to Business Object Wizard for display in the BO Properties dialog box.
Figure 66. Displaying the BO Properties dialog box
// Display BO Properties dialog box, initializing it with AgtProps Util.getBOSpecificProps(AgtProps, "For all the Tables selected");
Once users have specified values for the business-object properties in the BO Properties dialog box and clicked Next, Business Object Wizard sends the user-specified values back to the ODA. The ODA can retrieve these values in either of the following ways:
The getBOSpecificProps() call in Figure 66 did not save the Hashtable object that Business Object Wizard creates. Therefore, this code fragment uses the getBOSpecificProperty() method to get the value of the properties specified for the verbs and each business object prefix:
// Get the value of the Verbs and the Prefix properties AgentProperty propVerb = Util.getBOSpecificProperty("Verbs"); AgentProperty propPrefix = Util.getBOSpecificProperty("Prefix");
The generateBoDefs() method must generate a business object definition for each source node in the array it receives as an argument from Business Object Wizard. To generate a business object definition, generateBoDefs() takes the following steps:
The ODK API represents a business object definition as a business-object-definition (BusObjDef) object. You can use the BusObjDef() constructor to instantiate the new business object definition and provide it with a name. You can then provide the business object definition with the information shown in Table 40.
Contents of a business object definitionAs Table 40 shows, a business object definition contains both metadata and data. The following sections describe how to access these parts of the business object definition:
As Table 40 shows, the metadata of a business object definition consists of the following information:
The generateBoDefs() method receives the list of user-selected source nodes as an argument. This list is an array of String objects that contains the node paths for the user-selected source nodes. (For information on node paths, see Determining the parent-node path.) With this array, the ODA must create the appropriate name for the business object definition associated with each source node. Usually, the assumption that the ODA makes is that the name of the business object definition matches (or is based on) the name of the data-source object that the source node represents. The ODA must parse the source-node path to obtain the name of the source node, use this source-node name to locate the associated data-source object, then obtain the name from the data-source object.
For example, in the Roman Army sample, the names of the data-source objects and business object definitions match. Therefore, the sample code calls the findSon() method (defined in the ArmyAgent3 and ArmyAgent4 classes) to obtain the data-source object that the source node represents using the source node's node path from the input array of source nodes (nodes), as follows:
for (int i=0; i<nodes.length; i++) { Son sonNode = findSon(nodes[i]); BusObjDef sonBo = new BusObjDef(sonNode.name.getValue()); ...
The findSon() method parses the source-node path to obtain the name of the last node in the path.
As another example, suppose the data source is a database and its source nodes represent tables. If the source-node paths include the schema names (schema:table), your ODA needs to parse the source-node paths to assign just a table name to the corresponding business object definitions. If your ODA supports a user-specified prefix for business object definitions (with a configuration variable), the ODA must prepend this prefix before it calls BusObjDef() constructor to create the business-object-definition object, as the following code fragment shows:
AgentProperty propPrefix = getBOSpecificProperty("Prefix"); for (int i=0; i<names.length; i++) { strToken = new StringTokenizer(names[i], ":"); schemaName = strToken.nextToken(); tableName = strToken.nextToken()
if (propPrefix.allValues != null && propPrefix.allValues[0] != null) boDef = new BusObjDef(propPrefix.allValues[0] + tableName); else boDef = new BusObjDef(tableName); ...
If your data-source objects do not have the exact names you want to assign to your business object definitions, the ODA must parse or in some way format these names as needed.
As Business object application-specific information describes, application-specific information is a powerful way to put application-specific processing information within the business object definition. By moving this information from the processing program (such as a connector), the processing program can be metadata-driven; that is, it can be written in a more generic fashion and obtain its application-specific processing instructions from the business object definition. Therefore, if your business object definitions are to be used with metadata-driven processing programs, it is important that they include the correctly formatted application-specific information at the business-object, attribute, and verb levels.
The business object definitions that the Roman Army sample generates do not provide application-specific information. However, suppose the data source was a database with tables as its source nodes. The ODA would generate business object definitions for each user-selected table. In this business object definitions, you might include the name of the table as business-object-level application-specific information. The following code fragment uses the setAppInfo() method, defined in the BusObjDef class, to create the appropriate name-value pairs for this business-object-level application-specific information:
boDef.setAppInfo("TN=" + tableName + ";SCN=" + schemaName +";");
This code creates the TN and SCH name-value pairs to represent the table and schema names, respectively. It concatenates the table name and schema name with the tag used to name the element. It then uses the setAppInfo() method to assign this entire string as the business-object-level application-specific information.
A business object definition contains attributes, which describe the object that the business object definition represents. The business object definition holds the attributes in its attribute list. The ODK API represents an attribute as an attribute (BusObjAttr) object. To instantiate an attribute object, use the BusObjAttr() constructor.
Table 41 summarizes the properties in an attribute object. These properties correspond to the attribute metadata.
Table 41. Properties of an attribute
In the sample Roman Army ODA, each business object definition represents a Roman soldier. The generatesBoDefs() method creates the following attributes for business object definition:
Figure 67 contains a code fragment that creates these attribute objects for the business object definition.
Figure 67. Generating attributes
// 1. Create an attribute object for Age attribute BusObjAttr attr = new BusObjAttr("Age", BusObjAttrType.INTEGER, BusObjAttrType.AttrTypes[BusObjAttrType.INTEGER]);
// Set the Age attribute as the business object definition's key attr.setIsKey(true);
// Add the attribute to the business object definition's attribute list sonBo.insertAttribute(attr);
// 2. Create an attribute object for ChildNo attribute attr = new BusObjAttr("ChildNo", BusObjAttrType.INTEGER, BusObjAttrType.AttrTypes[BusObjAttrType.INTEGER]);
// Set the default value to number of children attr.setDefault(sonNode.Son == null ? "0" : "" + sonNode.Son.size());
// Add the attribute to the business object definition's attribute list boDef.insertAttribute(attr);
To create the Age attribute, the code fragment in Figure 67 takes the following steps:
To initialize the type, the code specifies the attribute-type constant for Integer ( BusObjAttrType.INTEGER). To initialize the type name, it uses the AttrTypes member variable in the BusObjAttrType interface. This static member variable provides the type names for all supported attribute types and can be indexed by the attribute-type constants. In this way, you can assign the type name without hardcoding the type-name string.
Because this form of the BusObjAttr() constructor specifies only three attribute properties, all other attribute properties default to "undefined". Therefore, after the BusObjAttr() call, the primary-key attribute property is false. To indicate that the Age attribute is the key attribute, the code sample calls setIsKey().
The code fragment in Figure 67 repeats these basic steps to generate the ChildNo attribute. The main difference is that because ChildNo is not the key attribute, no call to setIsKey() is needed. However, the code fragment does provide a default value for this attribute by calling the setDefault() method.
The business object definitions that the Roman Army sample generates are very simple. Only two attributes exist in the business object definition and their names are known at compile time. In addition, only a few attribute properties must be set. For a more complex example, suppose the data source was a database, with tables as its source nodes and as the names of its business object definitions. In this case, the database columns would correspond to the attributes of the business object definition. Many more of the attribute properties would need to be set for these attributes.
The following code fragment creates attributes for the columns in a database table:
Vector Attributes;
// 1. Retrieve columns from database table into 'rst' result set try{ ResultSet rst = null;
// Retrieve columns from database rst = db.dbmd.getColumns(null, schemaName, tableName, "%"); String colName = null; String colType = null; int cType = 0; int colSize = 0;
// Obtain next column from result set rst.next(); do{ // Get column name & type colName = rst.getString(4); colType = rst.getString("DATA_TYPE"); // Convert database types to supported types. // Load converted types into the cType variable // (steps not shown)
// 2. Create an attribute object for each column in the result set. Attributes = new Vector(1, 10);
try { // Create attribute object for column BusObjAttr attrib = new BusObjAttr(colName, cType);
// Set the cardinality and maxLength attribute properties attrib.setCardinality(BusObjAttr.CARD_SINGLE); colSize = rst.getInt("COLUMN_SIZE"); attrib.setMaxLength(colSize);
// Determine whether it is a primary key in the table: compare // column name against earlier retrieve of table's primary keys // (stored in pKeys -- code not included here) if (pKeys.contains(colName))== true { attrib.setIsKey(true); }else attrib.setIsKey(false);
// Determine whether it is a foreign key in the table: compare // column name against earlier retrieve of table's primary keys // (stored in fKeys -- code not included here) if (fKeys.contains(colName))== true { attrib.setIsForeignKey(true); }else attrib.setIsForeignKey(false);
// Set the isRequired property if ((rst.getString("IS_NULLABLE").equals("NO")) && (attrib.isKey() != true)){ attrib.setIsRequiredKey(true); }
// Create attribute application-specific information: // CN tag provides column name String asi = "CN="+colName; attrib.setAppText(asi); attrib.setDefault("");
// Add attribute object to Attributes vector Attributes.add(attrib); ...
// 3. Save the attribute vector as the business object definition's attribute list boDef.setAttributeList(Attributes);
The steps in this process are as follows:
BusObjAttr attrib = new BusObjAttr(LineItems, OrderLineItems); attrib.setCardinality(BusObjAttr.CARD_MULTIPLE);
For business object definitions generated for database tables, you might include the name of the column as attribute-level application-specific information for each attribute in the business object definition. This code fragment uses the setAppText() method, defined in the BusObjAttr class, to create the CN name-value pair for the attribute-level application-specific information. The code concatenates the column name with the CN tag. It then uses the setAppText() method to assign this entire string as the attribute's application-specific information.
A business object definition contains supported verbs, which describe the operations that can be performed on business objects of that business object definition. The business object definition holds its supported verbs in its verb list. The ODK API represents a verb as a business-object-verb (BusObjVerb) object. To instantiate a verb object, use the BusObjVerb() constructor.
Table 42 summarizes the metadata in a verb object.
In the Roman Army sample, the generatesBoDefs() method assigns to each business object definition one supported verb of Create. The following code fragment uses the insertVerb() method, defined in the BusObjDef class, to add the Create verb to the business object definition's verb list:
sonBo.insertVerb("Create", null);
The business object definitions that the Roman Army sample generates do not provide application-specific information. Therefore, the second argument to this insertVerb() call, which provides verb application-specific information, is null.
The ODA can use the BO Properties dialog box to obtain verb support for the generated business object definitions. By defining a business-object property called Verbs and allowing users to select the supported verbs, the ODA can obtain more customized verb support. For more information on the use of the BO Properties dialog box, see Requesting business-object properties.
The following code fragment assumes that the ODA has obtained user-specified values for a business-object property called Verbs and uses this property to obtain the verbs to the business object definition's verb list.
Vector Verbs; AgentProperty propVerbs = getBOSpecificProperty("Verbs"); if (propVerbs.allValues[0] != null) { int len = propVerbs.allValues.length; BusObjVerb verb; for(int i=0; i<len; i++) { if(propVerbs.allValues[i] != null) { try { verb = new BusObjVerb(propVerbs.allValues[i].toString(), ""); Verbs.add(verb); } } } ... boDef.setVerbList(Verbs);
This code fragment uses the BusObjVerb() constructor to copy a verb into the verb variable of type BusObjVerb. It then loads a String version of that verb object into the Verbs vector. The code does not specify verb application-specific information. Finally, the code fragment uses the setVerbList() method, defined in the BusObjDef class, to assign the generated verbs vector (Verbs) as the verb list of the business object definition.
As discussed in Providing generated content, the ODA must return the generated content to Business Object Wizard in two parts. Therefore, if the ODA generates business object definitions as content, it must return the following:
Because the ODA must generate business object definitions "on request", the generateBoDefs() method provides this content information, as follows:
Once Business Object Wizard receives this content-metadata object, it can access the generated business object definitions (within the generated-content structure) as needed with the getBoDefs() method.
The code sample in Figure 68 shows the last part of the generateBoDefs() method for the sample Roman Army ODA.
Figure 68. Providing generated business object definitions
m_generatedBOs.add(sonBo); } // this for loop terminates when all bus obj defs are generated
return new ContentMetaData(ContentType.BusinessObject, -1, m_generatedBOs.size()); } // end of generateBoDefs()
Figure 68 handles the generated content as follows:
As shown in Figure 63, the Roman Army sample uses a Java vector called m_generatedBOs as its generated-content structure. This structure is global to the methods of the Roman Army ODA class. To save the business object definition it has generated, generateBoDefs() saves it in the m_generatedBOs vector. This step is within a loop that terminates when the ODA has generated business object definitions for all source nodes that users selected. When Business Object Wizard needs to access the generated-content structure, it calls the content-retrieval method, getBoDefs().
The generateBoDefs() method instantiates a ContentMetaData object and into this constructor passes the information shown in Table 43.
Table 43. Initializing the content metadata for business-object-definition generation
ContentMetaData information | Code | Description |
---|---|---|
Content type | ContentType.BusinessObject |
Indicates that the content type is business object definitions |
Size of the generated content | -1 |
Indicates that total size is not required. The length value is not needed in the current implementation of a ContentMetaData object. |
Count of the generated content | m_generatedBOs.size() |
The size() method returns the number of elements currently in the vector. |