business objects programmer's guide

Document Sample
business objects programmer's guide Powered By Docstoc
					Amdocs ClarifyCRM 13.1


Business Objects
Programmer’s Guide




M20027-00E13010001
August 2005

Note: ClarifyCRM 13.1 is equivalent to Amdocs 6.0.
© 1995–2005, Amdocs. All Rights Reserved.
Amdocs Confidential
This manual and the software described herein are subject to your Amdocs Software
License and are copyrighted with all rights reserved. This manual and the software may
not be copied, in whole or in part, without written consent of Amdocs, except as permitted
by your Amdocs Software License.
The information in this manual is furnished for informational use only by authorized
persons, is subject to change without notice, and should not be construed as a
commitment by Amdocs. Amdocs assumes no responsibility or liability for any errors or
inaccuracies that may appear in this book.
The trademark and service marks of the respective Amdocs companies, including the
Amdocs mark and logo, are the exclusive property of such companies, and may not be
used without permission. All other marks mentioned in this material are the property of
their respective owners.


For technical assistance, visit http://clearanswer.clarify.com to submit a case at any time.
            Contents


            About This Guide
            Audience . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
            Related Documents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
            Organization of This Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
            What’s New in This Release . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
            Changes in the ClarifyCRM 12.5 Release . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
            Conventions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
                 Convention for Referring to Deployed Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20



Part I      The Basics of Business Objects

Chapter 1   Understanding ClarifyCRM Business Objects (CBOs)
            What Are CBOs? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
                 How Business Objects Provide Access to the Database. . . . . . . . . . . . . . . . . . . . . . . 24
                 How Business Objects Encapsulate Application Logic . . . . . . . . . . . . . . . . . . . . . . . . 26
            Understanding the Types of Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
                 Using Generic Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
                 Understanding the Hierarchy of Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
                 How Business Objects Implement the Workflow Paradigm . . . . . . . . . . . . . . . . . . . . . 30
                      How Business Objects Implement Ownership . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
                      How Business Objects Implement Conditions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
            Understanding XBeans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
            Understanding ClarifyCRM Value Objects (XVOs) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
            Examples of Using Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
                 Example of Using Business Objects in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
                 Example of Using Business Objects in JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
                 Example of Using Business Objects in Visual Basic . . . . . . . . . . . . . . . . . . . . . . . . . . 37
                 Example of Using Business Objects in VBScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
            Learning More About CBO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

Chapter 2   Querying and Updating Data in the Database
            Overview: Data Operations and Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
            Using Business Objects to Query the Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
                 Comparing SQL Clauses with Business Object Properties . . . . . . . . . . . . . . . . . . . . . 43
                 Specifying the Fields to Retrieve . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
                 Specifying the Table to Query . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
                 Specifying a Search Filter for the Current Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
                 Example of a Date-Time Search Filter. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46


                                                                                                                                                      3
CONTENTS




                Example of Using Submitted Values in Search Filters. . . . . . . . . . . . . . . . . . . . . . . . . 47
                Specifying a Search Filter for Related Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
                Specifying Sort Order. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
                Example of Setting Up Search Criteria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
                Querying the Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
           Getting Data from the Rowset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
                Navigating Through the Rowset. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
                Accessing Data from the Currently Selected Row . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
                Using the Field Object to Get Data from the Row . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
                     Getting Field Objects by Name and Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
                     Getting Field Objects by Generic Field ID . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
                     Getting Field Objects by Enumeration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
                     Getting the Value from a Field Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
                     Getting Information About a Field . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
                Getting the Object ID of a Row . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
           Example of Querying the Database. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
           Paging Search Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
           Using Business Objects to Save Data to the Database . . . . . . . . . . . . . . . . . . . . . . . . . . 68
           Adding New Rows in Database Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
                Adding a New Row to the Rowset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
                Generating the Object ID and ID Number for a New Row . . . . . . . . . . . . . . . . . . . . . . 71
                Example of Adding a New Row . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
                Adding a Row by Duplicating an Existing Row . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
                Setting the Numbering Scheme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
           Specifying Query Hints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
                BaseBO.setQueryHint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
                BaseBO.getQueryHint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
                Notes for Using setQueryHint and getQueryHint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
                     Query Hint Validity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
                     Table References. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
                     Executing a Query with QueryMode set to cbSubmitRequery . . . . . . . . . . . . . . . . 77
                     OR-operators in the FilterRelated Property . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
           Setting Field Values. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
           Deleting Rows from the Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
           Setting Constraints for Updates. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
           Committing Changes to the Database. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
           Refreshing Cached Data in the Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
           Example of Modifying Contact Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
           Querying and Updating Data Directly with SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
                Creating the SqlExec Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
                Specifying a SQL Statement by Setting Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
                Explicitly Specifying a SQL Statement. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88


4
                                                                                                                                                 CONTENTS




                 Calling a Stored Procedure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
                 Getting the Results from the SqlExec Object. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
            Working with Field Values and Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
                 About Field Values and Their Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
                 Working with String Representations of Values. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
                      How Locale and Time Zone Affect Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
                      Getting String Representations of Field Values . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
                      Specifying Custom Formats for String Representations . . . . . . . . . . . . . . . . . . . . 96
                      Converting a String Representation to a Value . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
                      Setting Field Values to String Representations . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
                 Working with Decimal and Currency Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
                      Getting String Representations of Currency Values . . . . . . . . . . . . . . . . . . . . . . . 99
                      Calculating Decimal and Currency Values. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
                 Working with Date-Time Values. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
                      Getting the Value of a Date-Time Field . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
                      Setting the Value of a Date-Time Field . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
                      Getting the Current Date and Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
                      Calculating Date-Time Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
                      Overriding Display in Master Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
                      How Text Time Stamp Formats Affect CBO . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
            Getting Metadata for Your Application. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
                 Working with ClarifyCRM Choice and User Choice Lists. . . . . . . . . . . . . . . . . . . . . . 108
                      Background: Lists of Choices in ClarifyCRM Applications . . . . . . . . . . . . . . . . . . 109
                      ClarifyCRM Choice Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
                      User Choice Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
                      How CBO Applications Access Lists of Choices . . . . . . . . . . . . . . . . . . . . . . . . . 112
                      Use of Reference Id for Choice Objects. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
                      Choice Lists and Status Transitions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
                 Working with Strings from the string_db Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
                 Updating Cached Metadata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123

Chapter 3   Working with XVOs
            Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
            Understanding the Naming Convention for XVOs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
            Creating an XVO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
            Copying Data from Business Objects to XVOs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
                 Copying a Row in a Business Object to an XVO . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
                 Copying a Rowset from a Business Object to an Array of XVOs. . . . . . . . . . . . . . . . 129
            Getting and Setting Field Values in an XVO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
                 Understanding the Java Types Used for Database Data Types . . . . . . . . . . . . . . . . 130
                 Getting Field Values from an XVO. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
                 Setting Field Values in XVOs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132


                                                                                                                                                        5
CONTENTS




                            Getting the Object ID from an XVO for Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
                            Getting and Setting Currency Values. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
                            Getting and Setting Values for Choice Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
                                 Understanding How Choice Fields Are Represented in XVOs . . . . . . . . . . . . . . 134
                                 Getting the Displayed Title of Currently Selected Choice . . . . . . . . . . . . . . . . . . 135
                                 Getting Details on the Currently Selected Choice . . . . . . . . . . . . . . . . . . . . . . . . 135
                                 Setting a Choice Field . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
                            Getting and Setting Flexible Attributes in an XVO . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
                                 Copying Flexible Attributes from a Business Object to an XVO. . . . . . . . . . . . . . 139
                                 Getting Flexible Attributes from an XVO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
                                 Getting the Value of a Flexible Attribute. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
                                 Setting Flexible Attributes in an XVO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
                            Tracking Unsaved Changes in XVO Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
                       Copying Data from XVOs to Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
                       Using an XVO to Add a New Row . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
                       Using an XVO to Identify an Existing Row . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
                            Identifying a Row by Object ID. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
                            Identifying a Row by Fields in a Unique Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
                            Identifying a Row by Fields in Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
                                 Using the Customization Center to Define a Named Lookup. . . . . . . . . . . . . . . . 153
                                 Using the Data Exchange Utility (dataex) to Import a Named Lookup. . . . . . . . . 155
                                 Example of Using Named Lookups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
                            Identifying a Row in a View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
                            Finding the Row for an XVO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
                       Using XVOs for Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
                       Determining and Changing the Mode of an XVO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
                       Updating the XVO Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
                            Understanding How Changes Affect XVO Classes . . . . . . . . . . . . . . . . . . . . . . . . . . 162
                            Running the XvoGen Tool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
                            Checking the Results of XvoGen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
                       Generating and Using Custom XVOs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
                            Creating the XVO Definition File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
                            Generating the Custom XVO Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
                            Compiling the Custom XVO Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
                            Example of Defining a Custom XVO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176

           Chapter 4   Working with Database Relations
                       Overview: Relations and Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
                            Understanding Parent-Child Relationships . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
                                 Querying the Parent Business Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
                                 Selecting Rows in the Child Business Object . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
                                 Selecting Rows in the Parent Business Object . . . . . . . . . . . . . . . . . . . . . . . . . . 184


6
                                                                                                                                        CONTENTS




                     Incrementally Retrieving Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
                     Updating the Parent Business Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
                Setting Up Parent-Child Relationships. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
                     Setting Up Relationships in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
                     Setting Up Relationships in JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
                     Setting Up Relationships in Visual Basic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
                     Example of Using a Parent-Child Relationship . . . . . . . . . . . . . . . . . . . . . . . . . . 188
                Setting Up Hierarchies of Parent-Child Relationships . . . . . . . . . . . . . . . . . . . . . . . . 190
                Controlling Queries and Updates in Child Objects. . . . . . . . . . . . . . . . . . . . . . . . . . . 196
                     Disabling Automatic Query for Child Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
                     Disabling Automatic Updates for Child Objects . . . . . . . . . . . . . . . . . . . . . . . . . . 198
            Establishing Database Relations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
                Establishing a Relation by Object ID and Name . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
                Establishing a Relation Through Parent-Child Objects . . . . . . . . . . . . . . . . . . . . . . . 201
                     Adding or Replacing a Child by Object ID . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
                     Adding or Replacing a Child by Copying . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
                     Adding a New Empty Row . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
            Removing Database Relations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
                Removing a Relation by Object ID and Name . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
                Removing Relations Through Parent-Child Objects . . . . . . . . . . . . . . . . . . . . . . . . . 212
                     Removing a Single Row. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
                     Removing Multiple Rows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
            Using Contained Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
                Getting Data from Contained Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
                Using Contained Objects to Establish Relations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220

Chapter 5   Working with Database Views
            Overview: Database Views and Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
            Using a View to Perform a Query . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
                Using a Separate Object to Query and Update Data. . . . . . . . . . . . . . . . . . . . . . . . . 226
                Using AddById to Avoid an Additional Query. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
            Setting Up a Business Object for a Related Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
            Setting Up an Updatable View. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
                Restrictions for Child Business Objects Under Views . . . . . . . . . . . . . . . . . . . . . . . . 235
                How Unique Fields Affect Child Business Objects. . . . . . . . . . . . . . . . . . . . . . . . . . . 235
                Guidelines for Child Business Objects Under Views . . . . . . . . . . . . . . . . . . . . . . . . . 236
            Setting Up a Child Business Object Hierarchy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237

Chapter 6   Working with Flexible Attributes
            Overview: Flexible Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
            Guidelines for Using Flexible Attributes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
            Getting and Setting Flexible Attribute Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243



                                                                                                                                               7
CONTENTS




                            Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
                            Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
                            Template Checking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
                       Using Views with Flexible Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
                       Using Flexible Attributes as Search Criteria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
                       Retrieving Templates and Flexible Attribute Definitions . . . . . . . . . . . . . . . . . . . . . . . . . 245
                       Creating New Templates and Flexible Attribute Definitions. . . . . . . . . . . . . . . . . . . . . . . 248
                       Calling the publish Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248

           Chapter 7   Working with Expressions
                       Overview: Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
                       Contexts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
                       Special Characters in Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
                       Using Expressions in a Focus Object Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
                       Using Expressions in a Database Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254
                       Using Expressions in a Database Context with Rowsets . . . . . . . . . . . . . . . . . . . . . . . . 255
                       Using Expressions in a Session Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
                       Using Expressions in an XML Document Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
                       Using Expressions in a Map Context. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
                       Using Expressions in a CDO Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261



           Part II     Building Your Application

           Chapter 8   Setting Up Your Application
                       Overview: Using CBO in an Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
                       Creating and Initializing the Application Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
                       Getting the Session Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
                            Tracking the User’s Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
                            Getting the Global Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
                            Creating a New Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
                            Tracking a Session with the Session ID. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
                       Setting the Locale and Time Zone. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
                            How CBO Uses Locale and Time Zone Information . . . . . . . . . . . . . . . . . . . . . . . . . 273
                            Setting Up Locales and Time Zones in the Database . . . . . . . . . . . . . . . . . . . . . . . . 273
                            Setting the Locale of the End User . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
                                 Getting the Locale from the Web Browser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
                                 Getting the List of Locales in the Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275
                                 Setting the Locale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
                            Setting the Time Zone of the End User . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282
                                 Getting the List of Time Zones in the Database. . . . . . . . . . . . . . . . . . . . . . . . . . 282
                                 Setting the Time Zone . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284



8
                                                                                                                                CONTENTS




    Example of Setting the Locale and Time Zone in JSP. . . . . . . . . . . . . . . . . . . . . . . . 284
         JSP Code for the Form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
         JSP Code for Handling the Form Submission . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
    Example of Setting the Locale and Time Zone in ASP . . . . . . . . . . . . . . . . . . . . . . . 287
         ASP Code for the Form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287
         ASP Code for Handling the Form Submission . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
Logging In to the Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
    Logging In as Different Types of Users . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
    Setting the Proxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291
    Setting the Timeout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291
    Logging in Using a Predefined Login . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291
    Logging in Using External Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291
    Changing Database Connection Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
Getting the Context for the Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296
Creating Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296
Getting Configuration and Profile Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297
Getting Diagnostic Information on CBO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298
Ending the CBO Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298
Working in Different Programming Languages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298
    Working with Business Objects in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
         Setting Up Your Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
         Specifying Constants and Flags. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
         Working with Currency and Decimal Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
         Logging Trace Messages. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
         Releasing Objects from Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
         Checking the Status of the Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
         Ending the Current Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
         Exiting the Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
    Working with Business Objects in JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
    Working with Business Objects in Visual Basic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
         Working with Dropped Controls in Visual Basic . . . . . . . . . . . . . . . . . . . . . . . . . . 303
         Programmatically Creating Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
         Writing CBO Applications for Microsoft Visual Basic .NET . . . . . . . . . . . . . . . . . 305
Writing Web Applications with ClarifyCRM Business Objects . . . . . . . . . . . . . . . . . . . . . 307
    Customizing ClarifyCRM web applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
    Developing Java Web Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
         Avoiding Use of JSP Declarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
         Creating the Application Object in a Java Web Application . . . . . . . . . . . . . . . . . 307
         Logging In and Tracking the User’s Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308
         Tracking the CBO Session Across Different JSP Servers . . . . . . . . . . . . . . . . . . 309
         Setting the Locale of the Current Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
         Creating the BoContext Object for the Web Application . . . . . . . . . . . . . . . . . . . 310


                                                                                                                                       9
CONTENTS




                                  Handling Multiple Threads Per HTTP Session. . . . . . . . . . . . . . . . . . . . . . . . . . . 310
                                  Releasing Objects After Processing an HTTP Request. . . . . . . . . . . . . . . . . . . . 310
                                  Releasing Objects After HTTP Session Timeouts . . . . . . . . . . . . . . . . . . . . . . . . 311
                             Developing Web Applications with ASP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
                                  Creating the Application Object in ASP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
                                  Logging In and Tracking the User’s Session in ASP . . . . . . . . . . . . . . . . . . . . . . 312

           Chapter 9    Using Global Transactions and Bulk Queries
                        Understanding Global Transactions and Bulk Queries . . . . . . . . . . . . . . . . . . . . . . . . . . 314
                        Using Global Transactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314
                             Understanding Enlistable Beans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
                             Understanding Nonenlistable Beans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
                             Understanding Query Beans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
                             Designing Beans to Support Global Transactions . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
                             Setting Up a Global Transaction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
                        Performing Bulk Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321
                        Performing Bulk Updates with XVOs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321
                             Adding or Updating Rows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322
                             Establishing and Removing Relations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324
                             Deleting Rows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326

           Chapter 10   Handling Errors
                        Overview: Error Handling in the Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328
                        Handling Errors in ActiveX. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328
                             Checking the Scripting Environment’s Error Object. . . . . . . . . . . . . . . . . . . . . . . . . . 329
                             Using the ClarifyCRM Error Object With ActiveX. . . . . . . . . . . . . . . . . . . . . . . . . . . . 329
                             Adding Errors to the Error Object in ActiveX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330
                             Passing Arguments to the Error Message . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331
                             Example of Adding a Custom Error . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331
                        Handling Errors in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
                             Handling a CboError Exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
                             Deriving Exception Classes from CboError . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333
                                  Reading the Error Message from the string_db Table . . . . . . . . . . . . . . . . . . . . . 333
                                  Specifying the Error Message in the Constructor. . . . . . . . . . . . . . . . . . . . . . . . . 335
                        Recovering from Database Update Failures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336
                        Adding Your Error Code to the string_db Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337

           Chapter 11   Writing Event Handlers
                        Overview: Events and Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 340
                        Writing Event Listeners in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 340
                        Writing Event Handlers in Visual Basic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342
                        Determining the Event Source. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342



10
                                                                                                                                                  CONTENTS




             Handling the FieldChanged Event . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344
             Handling the RowEnter Event . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345
             Handling the RowsetChanged Event. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345
             Examples of Handling CBO Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346
                  Java Example of a CBO Event Listener. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346
                  Visual Basic Example of CBO Event Handlers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350

Chapter 12   Using RemoteCB
             Overview: Executing RemoteCB from CBO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354
             Writing Your ClearBasic Subroutine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354
             Setting Up the Field Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355
             Configuring Your CBO Application for RemoteCB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356
             Setting Up the RemoteCB Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357
             Setting Up Parameters for the Subroutine. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357
             Calling the Subroutine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359
             Getting the Parameters Returned . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
             Resetting the Buffer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
             Releasing the RemoteCB Object. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363



Part III     Customizing Application Logic

Chapter 13   Customizing CBOs
             Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368
             Understanding How to Use Customized Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368
                  Extending Customized Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369
                  Adding Contained Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369
             Customizing a Business Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369
             Using the Class Map Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
             Understanding the Business Object Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
                       Adding Event Handlers to a Customized Business Object . . . . . . . . . . . . . . . . . 373
                  Listening for Events in Contained Business Objects . . . . . . . . . . . . . . . . . . . . . . . . . 373

Chapter 14   Writing and Customizing XBeans
             Understanding XBeans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 376
                  Understanding How ClarifyCRM Applications Use XBeans. . . . . . . . . . . . . . . . . . . . 376
                  Understanding XBean Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378
                  Example: XBean for Creating a New Row in a Custom Table. . . . . . . . . . . . . . . . . . 380
                  Example: Test Program for an XBean . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382
                  Understanding How XBeans Use Child XBeans . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
                  Example: XBean Using Child XBeans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384
             Defining the XBean Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385



                                                                                                                                                        11
CONTENTS




                        Defining Methods in an XBean . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387
                             Defining Accessor Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387
                                  General Guidelines for Defining Getter and Setter Methods . . . . . . . . . . . . . . . . 388
                                  General Guidelines for Defining Setter Methods . . . . . . . . . . . . . . . . . . . . . . . . . 388
                                  General Guidelines for Defining Getter Methods . . . . . . . . . . . . . . . . . . . . . . . . . 389
                             Understanding the Types Supported in Accessor Methods. . . . . . . . . . . . . . . . . . . . 390
                             Using Arbitrary Value Objects for Accessor Methods . . . . . . . . . . . . . . . . . . . . . . . . 390
                             Guidelines for Implementing the preExecute Method . . . . . . . . . . . . . . . . . . . . . . . . 391
                             Guidelines for Implementing the executeImpl Method. . . . . . . . . . . . . . . . . . . . . . . . 391
                             Guidelines for Implementing the postExecute Method . . . . . . . . . . . . . . . . . . . . . . . 392
                             Guidelines for Working with the BoContext Object . . . . . . . . . . . . . . . . . . . . . . . . . . 392
                        Throwing Exceptions in an XBean. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393
                             Guidelines for Throwing Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393
                             Propagating Checked Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394
                        Logging CBO Trace Messages for an XBean . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395
                        Setting Up Authorization Checks for an XBean . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
                             Identifying the Row Updated by Your XBean. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
                             Checking Authorization to Use an XBean . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397
                             Bypassing the Authorization Check . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397
                             Specifying Paths to XBeans in Authorization Expressions . . . . . . . . . . . . . . . . . . . . 398
                        Setting Up an XBean to Enlist in Global Transactions . . . . . . . . . . . . . . . . . . . . . . . . . . 399
                        Setting Up an XBean for Querying Only . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399
                        Customizing an XBean . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400



           Part IV      Appendixes

           Appendix A   Coding Guidelines for CBOs
                        Guidelines for Developing Web Applications. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406
                        Guidelines for Working with Object IDs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406
                        Guidelines for Working with Scripting Languages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407
                             Guidelines for All Languages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407
                             Guidelines for Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407
                             Guidelines for Visual Basic and VBScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 408
                             Guidelines for JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 408

           Appendix B   Configuration Items Used by CBO
                        Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 410
                        Database Codepage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 410
                        date_time entry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 410
                        display_mstr_time_zone . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 410
                        local_time format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411



12
                                                                                                                                   CONTENTS




Master Time Zone . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411
Server Time Zone . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411
site_time format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411
time stamp of application lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
time stamp of currency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
time stamp of locale. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
time stamp of string_db . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
time stamp of time_zone . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
time stamp of user lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413

Index




                                                                                                                                         13
CONTENTS




14
       P R E F A C E




       About This Guide



       This guide explains how to use the ClarifyCRM Business Objects (CBO) in your application or
       script code.




Audience
       This guide is intended for developers who need to write applications and scripts that query and
       update the ClarifyCRM database. The business objects are C++ objects with Java and ActiveX
       layers that you can use in your applications and scripts. For example, you can use the business
       objects to write a Visual Basic application, a Java application, or a web application consisting of
       JavaServer Pages (JSP) or Active Server Pages (ASP).

       To use this guide, you should be familiar with ClarifyCRM and its implementation at your site. You
       must also have a good understanding of the ClarifyCRM data model and your application
       development environment.




Related Documents
       For more information on CBO and the products that use it, see the following manuals on the
       ClarifyCRM Documentation CD:
           For information on installing CBO and configuring the CBO environment, see the System
           Administration Guide.
           For a complete description of the methods, properties, and events of the business objects, see
           the Core Business Objects Reference Guide.
           For information on the ClarifyCRM data dictionary and the data model, see the Data Dictionary
           Guide, the Data Dictionary for Objects, and the Data Dictionary for Views.




                                                                                                             15
Organization of This Guide




Organization of This Guide
                  This guide is organized in to the following parts:
                      Part I, The Basics of Business Objects, provides an introduction to CBO and the basic functionality
                      available with these objects.
                      Part II, Building Your Application, explains how to write an application that uses the business
                      objects.
                      Part III, Customizing Application Logic, explains how to write customized business objects and
                      XBeans.
                      Part IV, Appendixes, collects reference information and coding guidelines.




What’s New in This Release
                  The following features are new in this release:
                      XBeans are a new type of customizable Java class. These beans implement the business logic in
                      ClarifyCRM applications. For information on XBeans, see Chapter 14, Writing and Customizing
                      XBeans.

                  This release also introduces the following changes:
                      You can specify that you want new object IDs and ID numbers generated for a new row when
                      you call the BO.addNew method, instead of when you use the business objects to update the
                      database. For details, see Generating the Object ID and ID Number for a New Row on page 71.
                      To perform a bulk query, you can use the new query method of the BoContext object. For
                      details, see Performing Bulk Queries on page 321.

                      Note: In any new code that you write, do not use the Bulk object. While this object is still supported for
                      backward-compatibility, you should use the BoContext.query method to perform bulk queries and
                      global transactions to perform bulk updates. For details, see Chapter 9, Using Global Transactions and
                      Bulk Queries.

                      For Choice and ChoiceList objects, there is now a broader support for user-defined choice lists.
                      Non translatable keys (called reference Ids) are also introduced. Please see the Core Business
                      Objects Reference Guide for new and changed API related to this.
                      For XVOs, the following changes have been made:
                      – ClarifyCRM Value Objects (XVOs) are now covered in Chapter 3, Working with XVOs.
                      – To get the object ID of an XVO, you can call the new getObjid method of the XVO, as
                        explained in Getting the Object ID from an XVO for Table on page 132. This method returns the
                        object ID as a string.
                      – You can now access the flexible attributes for a row from an XVO. For details, see Getting and
                        Setting Flexible Attributes in an XVO on page 138.
                      – When you call the BoContext.resolveLookups method to find and copy the object ID
                        into an XVO, you can also specify additional fields that should be copied from the database to
                        the XVO. For details, see Finding the Row for an XVO on page 158.



16
                                                                                              About This Guide




   For XVOs representing views, the following changes have been made:
   – XVOs for views no longer have the following methods:

         getClfyObjid and setClfyObjid

         getLookupByObjid and setLookupByObjid
   – In previous releases, setter methods had empty implementations.
      In this release, these setter methods now have implementations, which are provided to
      support web services.
      Note that these setter methods are not intended for updating data. XVOs that represent views
      should be considered to be read only and should not be used to update data.
   – In previous releases, accessor methods passed in and returned a ClfyObjid object for fields
     containing object IDs. For example:
       public ClfyObjid getUserId()
       public void setUserId(ClfyObjid value)
      In this release, these accessor methods now pass in and return the object ID as a string.
       public String getUserId()
       public void setUserId(String value)
   For XVO methods that get and set choice fields, the following changes have been made, as a
   result of the new reference IDs that uniquely identify choices and choice lists:
   – While the following methods are still supported, you should not use these in newly written
     code:

         Vo.setXxx(String value) for choice fields (for example, CaseVo.setPriority and
         CaseVo.setSeverity)

         ClfyChoiceData.setChoiceName and ClfyChoiceData.setChoiceListName
      These methods pass in the displayable title of the choice or choice list, which are not
      guaranteed to be unique. Instead, you should use the following methods, which use the
      reference ID instead of the displayable title:

         Vo.setXxxChoiceData(ClfyChoiceData value) for choice fields (for example,
         CaseVo.setPriorityChoiceData and CaseVo.setSeverityChoiceData)

         ClfyChoiceData.setRefId and ClfyChoiceData.setListRefId
   – When using the constructors for the ClfyChoiceData class, specify the reference IDs of the
     choice and choice list, instead of the displayable titles.
      While these constructors still support the use of displayable titles, you should use reference
      IDs in any newly written code that uses these constructors:
       public ClfyChoiceData(String strRefId)
       public ClfyChoiceData(String strRefId, String strListRefId)
   The XvoGen tool no longer generates a separate file named CompilerMsg_XvoGenTest.log.
   All messages are generated in the XvoGen.log file or the CompilerMsg_XvoDbGen.log file.

Note: In addition to these changes, see the preface of the Core Business Objects Reference Guide for the
changes to the programming interface in this release.




                                                                                                           17
Changes in the ClarifyCRM 12.5 Release




Changes in the ClarifyCRM 12.5 Release
                 The following features were introduced in the ClarifyCRM 12.5 release:
                     Java CBO applications also provide lightweight objects in Java called ClarifyCRM Value Objects
                     (XVOs). XVOs are plain Java objects that encapsulate a row of data from a ClarifyCRM database
                     table or view. For more, read Understanding ClarifyCRM Value Objects (XVOs) on page 31 and
                     Chapter 3, Working with XVOs.
                     In a Java-based CBO application, you can define a global transaction that includes updates from
                     one or more business objects and beans that use business objects such as XBeans and save beans
                     that you create. For more, read Understanding Global Transactions and Bulk Queries on page 314.
                     You can now use plain old Java objects (POJOs), load beans, business objects, and
                     HttpServletRequests with expressions. For more, read Chapter 7, Working with Expressions.
                     There is a difference in behavior between Visual Basic .NET and Visual Basic 6. For more read
                     Writing CBO Applications for Microsoft Visual Basic .NET on page 305.

                 The ClarifyCRM 12.5 release also introduced the following changes:
                     A new Java class named BoContext is introduced in this release.
                     BoContext is a subclass of the existing FormContext class. BoContext was introduced as a
                     more intuitive name for this class for developers writing new code.
                     Existing application code that uses the Java FormContext class continues to be supported.
                     With the introduction of XVOs, this release includes a new set of XVO-related classes (classes in
                     the com.clarify.xvobase package), which are packaged in the ClfyCore.jar file.
                     Some of the existing CBO classes now extend or use the XVO-related classes. For example:
                     – The com.clarify.cbo.Money class now extends
                       com.clarify.xvobase.ClfyCurrency class.
                     – The com.clarify.cbo.CboConstants class now extends the
                       com.clarify.xvocase.XvoConstants class.
                     If you are compiling a Java class that uses CBO, you should include the ClfyCore.jar file in
                     your classpath.
                     During runtime, you do not need to explicitly include the ClfyCore.jar file in your
                     classpath.
                     For more information on setting up your Java development environment for using CBO, see
                     Setting Up Your Application on page 299.




18
                                                                                                    About This Guide




Conventions
       ClarifyCRM documents use the following conventions to highlight additional aspects, notify you
       of situations in which special care is required, or to point you to additional sources of information.

       Note: Provides additional information.



       Important: Highlights key considerations to keep in mind.



       Caution: Highlights situations that could potentially damage data or cause system failure.


       In addition, note the following conventions:
          Monospace is used for programming elements (such as code fragments, objects, methods,
          parameters, and HTML tags) and system elements (such as file names, directories, paths, and
          URLs).
          Monospace bold is used to distinguish system prompts or screen output from user responses,
          as in this example:
          username: oracle
          home directory: home\app\oracle
          Monospace italic is used for placeholders, which are general names that you replace with
          names specific to your site, as in this example:
          Clarify_home_directory\dbadmin\dbupgrade\
          Brackets signal options in command-line syntax:
          deRead [-llf log_filename] [-debug]
          The abbreviation BO refers to any type of business object. For example, the following statement
          uses the Count property, which applies to any type of business object (for example, Generic
          business objects, Case business objects, and so on):
          var nCount = BO.Count;




                                                                                                                 19
Conventions




              Convention for Referring to Deployed Files
              In ClarifyCRM products, EJBs are packaged in Enterprise Archive (EAR) files. Web applications are
              packaged in one of the following ways:
                 If you are using WebSphere Application Server, the web applications are packaged in Web
                 ARchive (WAR) format files, which in turn are packaged in EAR files.
                 If you are using WebLogic Server, the web applications are packaged in ZIP files.

              Before you can use or customize the product, the contents of the ZIP, EAR and WAR files need to be
              extracted:
                 If you are using WebSphere Application Server, deploying the EAR files automatically extracts
                 the contents of the EAR and WAR files.
                 If you are using WebLogic Server, you should extract the contents of the ZIP files before
                 deploying the application.

              This manual uses the notation clfy_app_root to refer to the directory containing the extracted
              files for the EJBs and web applications. The actual location of the clfy_app_root directory
              differs, depending on which J2EE Application Server you are using and how you deploy your
              application:
                 When you deploy an EAR file to WebSphere Application Server, the server extracts the files to
                 one of the following locations:
                   websphere_install_dir/installedApps/nodeName/application_name.ear (for
                   applications deployed to the default instance)
                   websphere_instance_dir/installedApps/nodeName/application_name.ear
                   (for applications deployed to a specific WebSphere Application Server instance)
                   deploy_dir/application_name.ear (for applications deployed to deploy_dir)
                 For WebLogic Server, you create a subdirectory (usually under the directory where you
                 installed the ClarifyCRM product) and extract the contents of the ZIP file to that subdirectory:
                   clfy_install_dir/new_subdirectory
                 When deploying the application, you select the subdirectory where you extracted the contents
                 of the ZIP file.

              The notation clfy_app_root refers to these locations.




20
P A R T    I




The Basics of Business Objects



CONTENTS
 CHAPTER 1, Understanding ClarifyCRM Business Objects (CBOs) 23

 CHAPTER 2, Querying and Updating Data in the Database 41

 CHAPTER 3, Working with XVOs 125

 CHAPTER 4, Working with Database Relations 179

 CHAPTER 5, Working with Database Views 223

 CHAPTER 6, Working with Flexible Attributes 241

 CHAPTER 7, Working with Expressions 249




                                                                  21
22
C H A P T E R     1




Understanding ClarifyCRM Business Objects
(CBOs)


CONTENTS
 What Are CBOs? 24

 Understanding the Types of Business Objects 27

 Understanding XBeans 31

 Understanding ClarifyCRM Value Objects (XVOs) 31

 Examples of Using Business Objects 34

 Learning More About CBO 39




                                                    23
What Are CBOs?




What Are CBOs?
                 ClarifyCRM Business Objects (CBOs) are C++ objects with Java and ActiveX layers that provide
                 access to data in the ClarifyCRM database. Each type of business object implements part of the
                 ClarifyCRM data model and encapsulates the application logic for working with that part of the
                 model.

                 Note: While the business objects are implemented in C++, and support a COM interface, writing C++
                 applications that access the COM layer of the business objects is not supported.


                 For example, the Case business object provides access to case-related data and includes methods
                 for finding, updating, and dispatching cases.

                 You can use CBO in any Java application (including JavaBeans and JavaServer Pages) or ActiveX
                 container (such as Visual Basic or Active Server Pages). For example, the JavaBeans and JSP pages
                 in the ClarifyCRM web applications use CBO to create new cases and opportunities.

                 Note: You can also use CBOs in Enterprise JavaBeans (EJBs). For details, see the Integration Gateway
                 Implementation Guide.


                 CBOs use the CBO Data Service to access the database. The CBO Data Service provides a logical
                 layer of abstraction above the physical vendor-specific database client software. This layer hides
                 the vendor-specific details of the database from CBO. (For more information on the CBO Data
                 Service, see the manual System Administration Guide.)

                 Finally, CBOs include objects that represent application-wide functionality (such as the ability to
                 log trace messages and access clarify.env and ClarifyEnv.xml settings), the session with the
                 ClarifyCRM database, and the context to that session for accessing the database. You use these
                 objects as part of the process of creating and using business objects in an application. (For details,
                 see Chapter 8, Setting Up Your Application.)

                 The following sections explain the capabilities of the business objects in more detail:
                    How Business Objects Provide Access to the Database on page 24
                    How Business Objects Encapsulate Application Logic on page 26


                 How Business Objects Provide Access to the Database
                 A business object has methods and properties that you can use to query and update data in a
                 ClarifyCRM database table.

                 Each business object contains a rowset, which is an in-memory copy of a set of rows from a
                 ClarifyCRM database table. When you query a database table, the business object holds the results
                 of the query in its rowset. You can select a row in the rowset and get any of the values of fields in
                 that row.

                 You can also use the rowset to make changes to data in the database table. You can modify the
                 values of fields in rows and commit the changed rows back to the database. If you want to add new
                 rows to the database table, you can add rows to the rowset in the business object and commit the
                 new rows to the database.

                 Figure 1 illustrates how business objects use rowsets to hold query results from the database.


24
                                                  CHAPTER 1   Understanding ClarifyCRM Business Objects (CBOs)




Figure 1   How business objects use rowsets to hold query results

   The rowset contains an
   in-memory copy of the
                                 id_number          title
 rows returned by the query.
                                 56                 S/W upgrade
                                 92                 Upgrade info           You can use the rowset to get
                                                                           the values of the fields in a
                                                                           given row (such as the case
                                                Rowset                     title or ID number).

                                         Case business object
                                        Your application or script



               The Case business object
               uses a database query to fill
               the rowset from the case
               table in the database.




                                  id_number         title
                                  56                S/W upgrade
                                  92                Upgrade info
                                  115               Need upgrade
                                  ...               ...
                                               Case table

                                          ClarifyCRM database

For example, the Case business object represents data in the case table (as well as data related to
cases). You can use the Case business object to query the database for all cases reported by a given
site. The Case business object fills its rowset with the rows from the case table that were returned
by the query. You can get the title of a case or the ID number of a case by getting the values of these
fields in a given row.

You can also change data in the rowset. You can change the title of a case or change the status or the
priority of a case by setting these fields to new values. You can create a new case by adding a new
row to the rowset. When you are done modifying the data in the in-memory rowset, you can
commit your changes to the database. The business object updates the case table (and any related
tables) to reflect the changes that you made to the in-memory rowset.

Figure 2 illustrates how business objects commit the changes made in rowsets to the database table.




                                                                                                           25
What Are CBOs?




Figure 2   How business objects use rowsets to update the database table



                                         id_number          title
                                                                               The rowset now contains
                                         56                 A new title        modified rows and newly
                                         92                 Upgrade info       added rows in memory.
                                                            A new case

                                                        Rowset

                                                 Case business object

                                                Your application or script

                          The Case business object commits
                          the modified data in the rowset to
                          the case table in the database.




                                          id_number            title
                                          56                   A new title
                                          92                   Upgrade info
                                          115                  Need upgrade
                                          116                  A new case
                                                       Case table

                                                   ClarifyCRM database



                 How Business Objects Encapsulate Application Logic
                 The business objects encapsulate the application logic for dealing with data in the
                 ClarifyCRM database. For example, suppose that you create a new case in the
                 ClarifyCRM classic client. In addition to inserting a new row in the case table, the client also
                 automatically generates a new case ID number (by getting the next available ID number from the
                 num_scheme table) and creates the appropriate activity log entries and time bombs to record the
                 creation of the case (by inserting new rows in the act_entry and time_bomb tables).

                 Similarly, the Case business object contains application logic to automatically handle these tasks.
                 When you use the Case business object to commit a new row to the case table, the Case business
                 object automatically generates a new case ID number, activity log entries, and time bombs. You do
                 not need to write application code to do this.

                 As another example, suppose you want to dispatch a case. As part of the process of dispatching the
                 case, you need to establish relations between the case and the queue, change the condition of the
                 case, and generate the activity log entries and time bombs to record the dispatch of the case.

                 The Case business object has a Dispatch method that automatically handles these tasks. You do
                 not need to write application code to update these relations and insert new rows in the act_entry
                 and time_bomb tables.


26
                                                          CHAPTER 1   Understanding ClarifyCRM Business Objects (CBOs)




Understanding the Types of Business Objects
        ClarifyCRM Business Objects provide access to part of the ClarifyCRM data model through its
        methods and properties. Examples of the different types of business objects include Case, Site,
        OEQuote, and Dialogue.

        Note: For a complete list of the business object types, see the Core Business Objects Reference Guide and the
        Sales Business Objects Reference.


        In Java, each type of business object is defined in a Java class. In ActiveX, each type of business
        object is registered as an ActiveX control.

        When you create a business object, you specify the type of business object that you want to create.
        You can specify the type in one of the following ways:
           In Java, you specify the class name (for example, com.clarify.cbo.Case or
           com.clarify.cbo.Site).
           In ActiveX, you specify the programmatic identifier (for example, Clarify.CBO.Case or
           Clarify.CBO.Site).

        The following sections explain the different types of business objects in more detail:
           Using Generic Business Objects on page 27
           Understanding the Hierarchy of Business Objects on page 28
           How Business Objects Implement the Workflow Paradigm on page 30


        Using Generic Business Objects
        ClarifyCRM Business Objects includes specific types of business objects for some of the
        ClarifyCRM database tables. If you need to access other database tables and views, you can use the
        Generic business object. The Generic business object is designed to query and update any
        ClarifyCRM database table or view.

        When you create a Generic business object, you set the DBObjectName property to the name of
        the table or view that you want to use. For example, if you want to use a Generic business object
        to query the user table, set the DBObjectName property to "user".

        Note: You cannot set the DBObjectName property to a name of an mtm_* table. The business objects
        handle these tables internally when you use the objects to establish relations or view related data. See
        Chapter 4, Working with Database Relations for details on how to use the business objects to manipulate
        database relations.


        Because the Generic business object is designed to work against any table or view, it does not
        contain the application-specific logic that other business objects include. For example, the Case
        business object has a Dispatch method with which you can dispatch a case. The Generic
        business object for the case table does not have this method.

        Using a Generic business object for a given table is not equivalent to using the specific type of
        business object for that table. For example, a Generic business object for the case table is not the
        same as a Case business object.



                                                                                                                   27
Understanding the Types of Business Objects




                  When you need to create or update rows in a database table, you should use a specific type of
                  business object rather than a Generic business object.

                  For example, if you want to update the case table, you should use the Case business object, not
                  the Generic business object. The Case business object updates the case.modify_stmp database
                  field and generates an activity log entry to record the modification. If you use a Generic business
                  object instead, none of these additional updates occur.

                  As another example, the Case business object contains application logic to generate the case ID
                  number, activity log entries, time bombs, and other related data. If you use a Generic business
                  object to add a case, none of this related data is generated.


                  Understanding the Hierarchy of Business Objects
                  In this manual, the term abstract class refers to a set of methods, properties, and events shared
                  among different types of business objects. All business objects have a base abstract class, which
                  includes methods for querying and updating the database (the Query and Update methods) and
                  properties for getting and setting data in the rowset (the Fields property).

                  In addition to the base abstract class, similar types of business objects share some of the same
                  application-specific methods and properties. For example, the Case, OEQuote, and Dialogue
                  business objects all represent workflow items. These business objects have methods for
                  dispatching, accepting, assigning, or yanking the workflow item. These business objects also have
                  properties to identify the current owner and WIPbin (work-in-progress bin) of the workflow item.
                  These methods and properties are part of a workflow abstract class.

                  Figure 3 illustrates some of the abstract classes shared by the different types of business objects. In
                  this diagram, the DataFields property and Query method are part of a base abstract class that
                  applies to all business objects. The Owner property and Dispatch method are part of a workflow
                  abstract class that applies only to business objects that represent workflow items (such as Case).

                  For more information on these abstract classes and the different types of business objects that use
                  these abstract classes, see Core Business Objects Reference Guide.




28
                                                                  CHAPTER 1   Understanding ClarifyCRM Business Objects (CBOs)




Figure 3   Abstract classes shared by different types of business objects

           Base abstract class
           - DataFields property
           - Query method
           - (Other base properties and methods)




                   Generic business object
                   - DBObjectName property



                   Workflow abstract class
                   - Owner property
                   - Dispatch method
                   - (Other workflow-specific properties and methods)



                           Case business object
                           - ResearchLog property
                           - ChangeContact method
                           - (other case-specific properties and methods)




                           (Other workflow business objects)



                   Log abstract class
                   - CreateActEntryForLog method



                           EmailLog business object
                           - Contact property



                           (Other log business objects)



                   ActEntry business object
                   - AddParticipant method
                   - CreateActEntry method




                   Contact business object
                   - ContactRole property
                   - (other contact-specific properties and methods)



                   (Other business objects)




                                                                                                                           29
Understanding the Types of Business Objects




                  How Business Objects Implement the Workflow Paradigm
                  Some types of business objects that represent workflow items (for example, the Case, OEQuote,
                  Dialogue, Subcase, and DemandDetail business objects). With these business objects you can
                  make certain types of changes in situations where the ClarifyCRM client application prevents the
                  end user from making those changes.

                  The following sections describe these differences between the ClarifyCRM client application and
                  the business objects in detail:
                     How Business Objects Implement Ownership on page 30
                     How Business Objects Implement Conditions on page 30

                  How Business Objects Implement Ownership
                  In the ClarifyCRM client application, only the owner of a workflow item (such as a case, order, or
                  dialogue) can modify that workflow item.

                  The business objects do not always prevent other users (aside from the owner) from modifying
                  workflow items. For example, you do not need to log in as the case owner in order to modify or
                  close a case with a Case business object. In ClarifyCRM eSupport, this means that web users can
                  close their cases without setting up the CBO session.Login method to use the case owner’s
                  login. (For details on this method, see Logging In to the Database on page 290.)

                  Note that the workflow business objects do ensure that workflow operations (such as dispatch,
                  assign, or move) can be performed only by the owner of the object. Specific types of business
                  objects may also include additional checks for ownership in certain methods.

                  If you want to prevent any user other than the owner from modifying a workflow item, your script
                  code should check the name of the current user (the user who is currently logged in) against the
                  name of the workflow item owner before any changes can be made and committed.

                  How Business Objects Implement Conditions
                  The ClarifyCRM client application prevents the end user from making certain types of
                  modifications to workflow items that are dispatched to a queue. For example, after you dispatch a
                  case to a queue, you cannot change the title, priority, severity, or type of the case. (You can,
                  however, log notes against the case.)

                  With the workflow business objects, you can make these types of changes to a dispatched
                  workflow item. For example, you can use the Case business object to change the title of a case that
                  has already been dispatched.

                  If you want to prevent a user from changing information in a dispatched workflow item, check the
                  condition of the item before the user makes and commits the changes.




30
                                                        CHAPTER 1     Understanding ClarifyCRM Business Objects (CBOs)




Understanding XBeans
        XBeans are customizable Java classes that implement business logic in ClarifyCRM applications.
        These classes are designed to use business objects to query and update data in the
        ClarifyCRM database.

        Figure 4    How XBeans use business objects

                                                      ClarifyCRM application
          Applications use XBeans to                    MySaveXB XBean
          implement business logic

                                            Case BO         Site BO            Contact BO


              XBeans use business
              objects to query and update
              the ClarifyCRM database


                                                           ClarifyCRM
                                                            database


        Some of the ClarifyCRM web applications, such as ClarifyCRM Quality, use XBeans to retrieve
        data for display and to save submitted data to the database. XBeans can also be used by Server
        Action Manager and Process Manager to implement services and process steps. Finally, in the
        ClarifyCRM Integration Gateway, you can set up XBeans to be called from EJBs and components
        that process requests in XML (such as the XML Engine, the XML Message Processor, and XML
        Processing MDB).

        You can customize the baseline XBeans provided with these applications or write your own XBeans
        to implement your own business logic. In your custom code, you can create and use business
        objects.

        For more information about XBeans, see Chapter 14, Writing and Customizing XBeans.




Understanding ClarifyCRM Value Objects (XVOs)
        CBO provides lightweight objects in Java called ClarifyCRM Value Objects (XVOs). XVOs are plain
        Java objects that encapsulate a row of data from a ClarifyCRM database table or view.

        You use XVOs to pass data to and from a business object. Business objects have methods that allow
        you to copy data between an XVO and the rowset in the business object.

        XBeans are also designed to use XVOs for input and output. For example, an application using an
        XBean can pass in an XVO to represent a new row to be added. The XBean, in turn, can pass this
        XVO to the business object for adding that row.




                                                                                                                   31
Understanding ClarifyCRM Value Objects (XVOs)




Figure 5    How business objects and XBeans use XVOs
                                               ClarifyCRM application or
                                                XBean in an application

                                                                                      You can also use XVOs to
      You can use XVOs to pass the                                                    retrieve the data from a row
      data for a row to a business                                                    in a business object.
      object (for example, to insert a
      new row in a database table).
                                                id_number       title
                                                56              S/W upgrade         id_number        title
     id_number       title                      92              Upgrade info        92               Upgrade info
     115             Need upgrade               115             Need upgrade
                                                                                            CaseVo (an XVO)
             CaseVo (an XVO)                          Case business object




                                                      Database queries and
                                                            updates




                                                id_number       title
                                                56              S/W upgrade
                                                92              Upgrade info
                                                115             Need upgrade
                                                ...             ...
                                                            Case table

                                                         ClarifyCRM database


                 Each table and view in the ClarifyCRM database has a corresponding XVO Java class. The Java
                 class is named after the table or view. For example, the XVO for the contact table is the
                 ContactVo class, and the XVO for the rol_contct view is the RolContctVo class. (In the XVO
                 class names, any underscores and hyphens in the original table name are removed, and the letter
                 after an underscore or hyphen is capitalized.)

                 For each field in a table, the corresponding XVO for the table provides a getter method and setter
                 method for that field. For example, the ContactVo class has a getFirstName method and a
                 setFirstName method for getting and setting the value of the first_name field in the contact
                 table row. (The same changes that are made to the class names with regard to underscores and
                 hyphens are also applied to the method names derived from field names.)

                 The getter and setter methods are strongly typed, providing access to the value of a field as a
                 specific type or class of object. In addition, since the method names include the field names, the API
                 for each XVO is self-descriptive. Problems with incorrectly entered field names (for example,
                 typographical errors in field names) are caught during compilation time.




32
                                                   CHAPTER 1   Understanding ClarifyCRM Business Objects (CBOs)




Table 1    XVOs provide access to data through strongly typed getters and setters

                                         CaseVo (an XVO)

                           id_number       title
                           115             Need upgrade



public String getIdNumber()                public String getTitle()
public void setIdNumber(String value)      public void setTitle(String value)


Unlike CBOs, XVOs do not contain any business logic and do not require a connection to the
ClarifyCRM database. XVOs are just containers for data. XVOs also do not rely on any of the CBO
Java classes (classes in the com.clarify.cbo package.)

As a result, you can use XVOs as a mechanism for passing the data for a row between a CBO
application and a non-CBO application. For example, if you have an EJB designed to use CBO to
query and update data in the database, you can use XVOs in an EJB client to pass data to and get
data from the EJB. XVOs allow the EJB client to access and specify data for the business objects
without direct use of CBO. (For details on writing EJBs that use CBO, see the Integration Gateway
Implementation Guide.)

Figure 6    How EJB clients use XVOs to pass data to and from the EJB

           My EJB client                                                                My EJB

                                                                                 MyCaseSaveXB XBean
            CaseVo for
            new case
                                                                                       Case BO


            CaseVo for
           created case

                                                                                       ClarifyCRM
                                                                                        database


For more information on XVOs, see Chapter 3, Working with XVOs.




                                                                                                            33
Examples of Using Business Objects




Examples of Using Business Objects
                 The following examples demonstrate how to use CBO in application code:
                     Example of Using Business Objects in Java on page 34
                     Example of Using Business Objects in JavaScript on page 35
                     Example of Using Business Objects in Visual Basic on page 37
                     Example of Using Business Objects in VBScript on page 38


                 Example of Using Business Objects in Java
                 This is a Java application that prints the number of sites in the database. This example
                 demonstrates how to use the business objects to query the site table.

                 Note: In order to compile and run this example, you need to follow the instructions on configuring your
                 application environment to use CBO. For details, see Working with Business Objects in Java on page 299
                 and in the online manual System Administration Guide.

                 // The business objects are part of the com.clarify.cbo package,
                 // so include this package in the example.
                 import com.clarify.cbo.*;
                 public class Example {
                    public static void main(String [] args) {

                         Application clfyApp = null;
                         Session clfySession = null;
                         BoContext clfyContext = null;
                         Generic boGeneric = null;

                         try {
                            // Create the ClarifyCRM Application object to load the
                            // appropriate libraries into memory.
                            System.out.println("Creating the Application object...");
                            clfyApp = new Application();

                             // Create the CBO Session object that you will use to
                             // establish a connection with the database.
                             System.out.println("Creating the Session object...");
                             clfySession = clfyApp.createSession();

                             // Use the Session object to log in to the database.
                             System.out.println("Logging in...");
                             clfySession.login("username", "password");

                             // Create the BoContext object that you will use to
                             // create business objects.
                             System.out.println("Creating the BoContext object...");
                             clfyContext = clfySession.createBoContext();

                             // Create a Generic business object for querying table_site.
                             System.out.println("Creating Generic business object...");
                             boGeneric = clfyContext.createGenericBO("site");
                             boGeneric.setDataFields("*");


34
                                                  CHAPTER 1   Understanding ClarifyCRM Business Objects (CBOs)




            // Query the business object.
            System.out.println("Querying the database...");
            boGeneric.query();

            // Print out the number of sites found.
            System.out.println("Sites found: " + boGeneric.getCount());

        } catch(CboError e) {

            // Get and print information about the error.
            String strErrorMsg = "Error: " + e.getNumber() + " " +
               e.getDescription();
            System.out.println(strErrorMsg);

        } catch(Exception e) {

            // Get and print information about the error.
            String strErrorMsg = "Error: " + e.getMessage();
            System.out.println(strErrorMsg);

        } finally {

            // Log out of the database.
            if (clfySession != null) {
               System.out.println("Logging out...");
               clfySession.release();
            }

            // Exit from the application.
            if (clfyApp != null) {
               System.out.println("Exiting application...");
               clfyApp.shutdown();
               clfyApp.release();
            }
        }
    }
}


Example of Using Business Objects in JavaScript
The following section of code is a script (written in JavaScript) that prints the number of sites in the
database. To execute this script, run the cscript utility and pass the name of this script as an
argument. For example:
C:\> cscript printSiteName.js

(cscript is the command-based version of Windows Scripting Host. It is included in Windows
Script 5.0 and more recent versions. Windows Script also includes the JScript and VBScript
scripting engines.)

Note: In order to run this example, you also need to follow the instructions on configuring your application
environment to use CBO. For details, see the online manual System Administration Guide.




                                                                                                           35
Examples of Using Business Objects




                 This example demonstrates how to use the business objects to query the site table and print the
                 number of sites found.
                 var ClfyApp, ClfySession, ClfyForm = null;
                 var BoGeneric;
                 var e, strErrorMsg;

                 try {
                    // Create the Clarify Application object to load the appropriate
                    // DLLs in to memory.
                    WScript.Echo("Creating the Application object...");
                    ClfyApp = WScript.CreateObject("Clarify.CBO.App.1");
                    // You can also use this statement to create the object:
                    // ClfyApp = new ActiveXObject("Clarify.CBO.App.1");

                     // Create the CBO Session object that you will use to establish
                     // a connection with the database.
                     WScript.Echo("Creating the Session object...");
                     ClfySession = ClfyApp.CreateSession();

                     // Use the Session object to log in to the database.
                     WScript.Echo("Logging in...");
                     ClfySession.Login("username", "password");

                     // Create the Clarify FormContext object that you will use to create
                     // business objects.
                     WScript.Echo("Creating the FormContext object...");
                     ClfyForm = ClfySession.CreateFormContext();

                     // Create a Generic business object for querying table_site.
                     WScript.Echo("Creating the Generic business object...");
                     BoGeneric = ClfyForm.CreateGenericBO("site");
                     BoGeneric.DataFields = "*";

                     // Use the FormContext object to query all business objects.
                     WScript.Echo("Querying the database...");
                     ClfyForm.Start();

                    // Print out the number of sites found.
                    WScript.Echo("Sites found: " + BoGeneric.Count);
                 } catch(e) {
                    // Get and print information about the error.
                    strErrorMsg = "Error: " + (e.number & 0xFFFF) + " " + e.description;
                    WScript.Echo(strErrorMsg);
                 }

                 // End the current session.
                 ClfySession.Logout();




36
                                                  CHAPTER 1   Understanding ClarifyCRM Business Objects (CBOs)




Example of Using Business Objects in Visual Basic
The following section of code is a Visual Basic application that prints the number of sites in the
database to the Immediate Window. This example demonstrates how to use the business objects to
query table_site and print results.

Note that you can also drop the business objects on a form instead of creating them
programmatically. For details, see Working with Dropped Controls in Visual Basic on page 303.

Note: In order to run this example, you also need to follow the instructions on configuring your application
environment to use CBO. For details, see Example of Using Business Objects in Visual Basic on page 37 and
in the online manual System Administration Guide.

Private Sub Form_Load()
   Dim ClfyApp
   Dim ClfySession As ClarifyCommonBusObj.Session
   Dim ClfyForm As ClarifyCommonBusObj.FormContext
   Dim BoGeneric As ClarifyCommonBusObj.Generic
   Dim strErrorMsg As String
   On Error GoTo ErrorHandler

   'Create the Clarify Application object to load the appropriate DLLs
   'in to memory.
   Debug.Print
   Debug.Print "Creating the Application object..."
   Set ClfyApp = CreateObject("Clarify.CBO.App.1")

   'Create the Clarify Session object that you will use to establish a
   'connection with the database.
   Debug.Print "Creating the Session object..."
   Set ClfySession = ClfyApp.CreateSession

   'Use the Session object to log in to the database.
   Debug.Print "Logging in..."
   ClfySession.Login "username", "password"

   'Create the Clarify FormContext object that you will use to create
   'business objects.
   Debug.Print "Creating the FormContext object..."
   Set ClfyForm = ClfySession.CreateFormContext

   'Create a Generic business object for querying table_site.
   Debug.Print "Creating the Generic business object..."
   Set BoGeneric = ClfyForm.CreateGenericBO("site")
   BoGeneric.DataFields = "*"

   'Use the FormContext object to query all business objects.
   Debug.Print "Querying the database..."
   ClfyForm.Start

   'Print out the number of sites found.
   Debug.Print "Number of sites found: " & BoGeneric.Count

   'End the current session.
   ClfySession.Logout



                                                                                                           37
Examples of Using Business Objects




                     'Unload the form.
                     Unload Me

                     Exit Sub

                 ErrorHandler:
                    'Get and display information about the error.
                    strErrorMsg = "Error: " & CStr(Err.Number) & " " & Err.Description
                    MsgBox strErrorMsg

                     'End the current session.
                     ClfySession.Logout

                     'Unload the form.
                     Unload Me

                 End Sub


                 Example of Using Business Objects in VBScript
                 The following section of code is a script (written in VBScript) that prints the number of sites in the
                 database. To execute this script, run the cscript utility and pass the name of this script as an
                 argument. For example:
                 C:\> cscript printSiteName.vbs

                 Note: In order to run this example, you also need to follow the instructions on configuring your application
                 environment to use CBO. For details, see the online manual System Administration Guide.

                 Dim ClfyApp, ClfySession, ClfyForm
                 Dim BoGeneric
                 Dim e, strErrorMsg

                 ' Create the Clarify Application object to load the appropriate
                 ' DLLs in to memory.

                 WScript.Echo"Creating the Application object..."
                 Set ClfyApp = WScript.CreateObject("Clarify.CBO.App.1")

                 ' Create the CBO Session object that you will use to establish
                 ' a connection with the database.

                 WScript.Echo "Creating the Session object..."
                 Set ClfySession = ClfyApp.CreateSession

                 ' Use the Session object to log in to the database.
                 WScript.Echo "Logging in..."
                 ClfySession.Login "sa", "freshen"

                 ' Create the Clarify FormContext object that you will use to create
                 ' business objects.
                 WScript.Echo"Creating the FormContext object..."
                 Set ClfyForm = ClfySession.CreateFormContext




38
                                                     CHAPTER 1   Understanding ClarifyCRM Business Objects (CBOs)




       ' Create a Generic business object for querying table_site.
       WScript.Echo "Creating the Generic business object..."
       Set BoGeneric = ClfyForm.CreateGenericBO ("site")
       BoGeneric.DataFields = "*"

       ' Use the FormContext object to query all business objects.
       WScript.Echo "Querying the database..."
       ClfyForm.Start

       ' Print out the number of sites found.
       WScript.Echo "Sites found: " & BoGeneric.Count

       ' End the current session.
       ClfySession.Logout




Learning More About CBO
       The first part of this guide provides more information about using the methods, properties, and
       events common to all types of business objects. Topics covered in this part include:
          How to use the business objects to query and update data (see Chapter 2, Querying and Updating
          Data in the Database)
          How to use the business objects to establish relations, remove relations, and view related data
          (see Chapter 4, Working with Database Relations)
          How to use the Generic business object to query and update database views (see Chapter 5,
          Working with Database Views)

       The second part of this guide explains how to set up your application to create and use business
       objects. Topics covered in this section include:
          How to write application code for establishing a session, creating the context for the business
          objects, and creating business objects (see Chapter 8, Setting Up Your Application)
          How to group database operations together into bulk queries and global transactions (see
          Chapter 9, Using Global Transactions and Bulk Queries)
          How to use the Error object to get and set error information (see Chapter 10, Handling Errors)
          How to use the events for the business objects in your application (see Chapter 11, Writing Event
          Handlers)
          How to call ClearBasic subroutines on the application server, if you implemented Flexible
          Deployment (see Chapter 12, Using RemoteCB)




                                                                                                              39
Learning More About CBO




40
C H A P T E R     2




Querying and Updating Data in the Database



CONTENTS
 Overview: Data Operations and Business Objects 42

 Using Business Objects to Query the Database 42

 Getting Data from the Rowset 52

 Example of Querying the Database 62

 Paging Search Results 65

 Using Business Objects to Save Data to the Database 68

 Adding New Rows in Database Tables 69

 Setting Field Values 78

 Deleting Rows from the Database 79

 Setting Constraints for Updates 80

 Committing Changes to the Database 83

 Refreshing Cached Data in the Session 84

 Example of Modifying Contact Information 85

 Querying and Updating Data Directly with SQL 86

 Working with Field Values and Data Types 92

 Getting Metadata for Your Application 108




                                                          41
Overview: Data Operations and Business Objects




Overview: Data Operations and Business Objects
                 As mentioned in Chapter 1, Understanding ClarifyCRM Business Objects (CBOs), each type of CBO
                 has properties and methods for querying and updating the database. The business objects also
                 support the ability to retrieve metadata (such as application pop-up lists and string values from the
                 string_db table) that you can use to build the user interface for your application. This metadata
                 is cached and does not require you to query the database.

                 The following sections explain how to use the business objects to perform these tasks:
                     To use a business object to query a database table and retrieve results, see Using Business Objects
                     to Query the Database on page 42.
                     To access the data in a rowset in a business object, see Getting Data from the Rowset on page 52.
                     To use a business object to modify data, add new rows, and commit changes to the database, see
                     Using Business Objects to Save Data to the Database on page 68.
                     To add, update, or delete rows from a database table, see Adding New Rows in Database Tables on
                     page 69, Setting Field Values on page 78, and Deleting Rows from the Database on page 79.
                     To directly specify the SQL statements that you want executed against a database, see Querying
                     and Updating Data Directly with SQL on page 86.
                     To use a business object to get metadata for your application, such as application pop-up lists
                     and strings from the string_db table, see Getting Metadata for Your Application on page 108.

                 Finally, the section Working with Field Values and Data Types on page 92 explains some important
                 guidelines for working with database fields that use different data types.




Using Business Objects to Query the Database
                 The business objects provide properties and methods that you can use to specify search criteria,
                 query database tables, and retrieve results. The following sections explain how to use these
                 properties and methods:
                     Comparing SQL Clauses with Business Object Properties on page 43
                     Specifying the Fields to Retrieve on page 43
                     Specifying the Table to Query on page 44
                     Specifying a Search Filter for the Current Table on page 44
                     Specifying a Search Filter for Related Tables on page 47
                     Specifying Sort Order on page 49
                     Querying the Database on page 51

                 To see an example of script code that sets up a query, see Example of Setting Up Search Criteria on
                 page 50. For a complete example of setting up and performing a query, see Example of Querying the
                 Database on page 62.




42
                                                          CHAPTER 2   Querying and Updating Data in the Database




Comparing SQL Clauses with Business Object Properties
In SQL, you execute a SELECT statement to query a database table. The SELECT statement consists
of clauses with which you can specify which fields to retrieve, which tables to query, which rows to
retrieve, and how to sort the rows returned by the query.

CBO has properties with which you can specify the same types of search criteria.

Table 2    Specifying query criteria in SQL and in the business objects
Search Criteria                            Corresponding         Corresponding Business Object Property
                                           SQL Clause
Determines which fields to retrieve        SELECT                DataFields
Determines which table or view to          FROM                  If you are not using a Generic business
query                                                            object, the type of the business object
                                                                 determines the table queried.
                                                                 If you are using a Generic business object,
                                                                 use the DBObjectName property to specify
                                                                 the ClarifyCRM table or view to query.
Determines which rows to retrieve          WHERE                 Filter, FilterRelated
Determines how to sort the results         ORDER BY              SortOrder, SortOrderRelated



Note: When using a business object to query the database, you should always specify a sort order for the
results. Do not assume that the results are returned in order of their object IDs. Also, do not assume that the
order of object IDs corresponds to the order in which the rows were inserted in the database. ClarifyCRM
applications can cache a range of object IDs for use when inserting new rows, return any unused object IDs,
and use those previously unused object IDs at a later time. As a result, object IDs are not always used to
insert rows in to the database at the time when the object IDs are generated.


As is the case with a SQL statement, these business object properties affect the query that is
performed, not any existing results. (For example, setting the BO.SortOrder property does not
change the sort order of any existing results. Similarly, setting the BO.Filter property does not
automatically hide or remove any existing results.)

Note: The business objects do not support the ability to generate a GROUP BY clause. To specify a query that
includes this type of clause, use the SqlExec object. You can also use the SqlExec object to explicitly
specify a SQL statement to execute. See Querying and Updating Data Directly with SQL on page 86.


The next sections describe how to use these properties in more detail.


Specifying the Fields to Retrieve
To specify the fields to retrieve, set the BO.DataFields property to a comma-delimited list of
field names. To retrieve all fields in the table, set the BO.DataFields property to *.

Note: You cannot specify SQL aggregate functions (such as COUNT, SUM, AVG, MIN, and MAX) in the
BO.DataFields property. If you want to specify a query that includes these types of functions, use the
SqlExec object. For details, see Querying and Updating Data Directly with SQL on page 86.



                                                                                                             43
Using Business Objects to Query the Database




                  In addition to the fields specified in the BO.DataFields property, the business objects also
                  automatically retrieve the objid field and any fields containing foreign keys (for example, the
                  case_reporter2contact field in the case table).

                  Specific types of business objects (other than the Generic business object) can also retrieve
                  additional fields. For example, the Case business object always retrieves the id_number, title,
                  and creation_time fields, even if you do not specify them in the BO.DataFields property.


                  Specifying the Table to Query
                  If you are using the Generic business object, you need to specify which table you want to query.
                  (The other types of business objects already apply to a specific database table, so you do not need to
                  specify this criterion.)

                  For the Generic business object, set the GenericBO.DBObjectName property to the name of the
                  table that you want to use. Omit the table_ prefix from the name of the table (for example, use
                  wipbin to query the wipbin table).

                  Note: After you set the DBObjectName property of the Generic business object and use the object to
                  access the database, you cannot change the value of this property. At this point, the business object has
                  already retrieved metadata for the table and fields specified by the DBObjectName property. You cannot use
                  this business object to access a different table or view.



                  Specifying a Search Filter for the Current Table
                  To specify search filter in terms of fields in the current table, use the BO.Filter property. To
                  specify a simple search filter, you can use the following format:
                  field_name operator value

                  The following example demonstrates how to set up a filter to find the case with the ID number 3:
                     In Java:
                     BoCase.setFilter("id_number = '3'");
                     In JavaScript or Visual Basic:
                     BoCase.Filter = "id_number = '3'"

                  Note: If you want to include fields on related tables in your search criteria, use the BO.FilterRelated
                  property. For details, see Specifying a Search Filter for Related Tables on page 47.


                  In addition to the standard relational operators, you can also use the SQL operators Like, Not Like,
                  Sounds Like, In, Is Null, and Is Not Null. When using the Like and Not Like operators, you can use
                  the wildcards “%” (to match any number of characters) and “_“ (to match a single character). See
                  the Core Business Objects Reference Guide for a complete list and description of operators.




44
                                                          CHAPTER 2   Querying and Updating Data in the Database




If you want to specify combinations of filters, you can use AND, OR, and parentheses to group the
filters. For example, if you want to find all contacts with an email address in the mycompany.com
domain and with the last name Jensen or Howes, set up a filter as shown below:
   In Java:
   BoContact.setFilter("(last_name = 'Jensen' OR last_name = 'Howes')
      AND e_mail like '%@mycompany.com'");
   In JavaScript or Visual Basic:
   BoContact.Filter = "(last_name = 'Jensen' OR last_name = 'Howes')
      AND e_mail like '%@mycompany.com'"

You can also specify SQL expressions. You can use the arithmetic operators +, -, *, and /. To specify
the order in which the operators are recognized, use parentheses.

For example, the following section of code checks the condition field in the condition table to
find any open conditions. The example uses the SQL expression FLOOR to get the value of the
second least significant bit in this field to determine if the bit is set. (The expression is equivalent to
the expression (x/4)%2 <> 1.)
   In Java:
   BoCondition.setFilter("FLOOR(condition/4)*2 <> FLOOR(condition/2)");
   In JavaScript or Visual Basic:
   BoCondition.Filter = "FLOOR(condition/4)*2 <> FLOOR(condition/2)";

If you are using operators other than +, -, *, and /, make sure to leave spaces around the operator.

Note: If you are writing code that will be run against different types of databases (for example, Microsoft
SQL Server and Oracle), make sure to use operators that are supported in these different types of databases.


When specifying a search filter, make sure you use language-neutral, SQL-compliant syntax:
   For numeric values, use a period as the decimal point.
   For date-time values, specify date-time values in terms of the master time zone (not the local
   time zone), and specify the value as a quoted string in the following format:
       'yyyy-mm-dd hh:mm:ss'

For example:
       BO.setFilter("modify_stmp < '1999-09-05 00:00:00'"); // In Java
       BO.Filter = "modify_stmp < '1999-09-05 00:00:00'" // In ActiveX
   For more information, see Working with Date-Time Values on page 102.
   Enclose string values in single quotes, and replace single quotes with two single quotes.
   For example, suppose that you want to find all sites with the following name:
       Company's Site
   The following statements set up a filter for this name:
   – In Java:
           BoSite.setFilter("name = 'Company''s Site'");
   – In JavaScript:
           BoSite.Filter = "name = 'Company''s Site'";


                                                                                                              45
Using Business Objects to Query the Database




                  To convert a value to SQL-compliant syntax, use the Session.ToSQLString method:
                     If the value is a string, the method returns a string surrounded by single quotes. The method
                     also replaces any single quotes within the string with two single quotes.
                     If the value is an object of the java.util.Calendar class or if the value is a variant date, the
                     method returns the date as a string in ISO format, surrounded by quotes. (For an example of
                     using this method, see Example of a Date-Time Search Filter on page 46.)
                     Before passing a date-time value to the Session.ToSQLString method, express the value in
                     terms of the local time zone (for example, the time zone of the web server machine). The
                     method converts the value from the local time zone to the master time zone.
                     If the value is a string representation of a non string value submitted by the end user, use the
                     following syntax for the Session.ToSQLString method:
                         Session.ToSQLString(strValue, cboSQLQuote, cboDataType);
                     For the cboDataType argument, specify the data type of the original value (for example,
                     cboTypeInteger for integers or cboTypeDateTime for date-time values).
                     In Java, these constants are defined as static fields in the CBOConstants class:
                         Session.toSQLString(strValue, CboConstants.cboSQLQuote,
                            CboConstants.cboDataType);
                     For string representations of date-time values, the method converts the value from the end
                     user’s time zone to the master time zone and returns a string representation of that converted
                     value, surrounded by single quotes. If you do not want to convert the value between time zones
                     and to surround the value with single quotes, specify cboSQLLanguageNeutral instead of
                     cboSQLQuote.
                     For an example of using the Session.ToSQLString method in this way, see Example of Using
                     Submitted Values in Search Filters on page 47.


                  Example of a Date-Time Search Filter
                  The following statements set up an OEQuote business object to find quotes that have not yet
                  expired:
                     In Java:
                     ...
                     BoQuote.setFilter("expire_date >= " +
                        ClfySession.toSQLString(ClfySession.getCurrentDate()));
                     BoQuote.setFilterRelated("contract2condition;condition <= 1024");
                     ...
                     In JavaScript:
                     ...
                     BoQuote.Filter = "expire_date >= " +
                        ClfySession.ToSQLString(ClfySession.GetCurrentDate());
                     BoQuote.FilterRelated = "contract2condition;condition <= 1024";
                     ...

                  Note the use of the Session.ToSQLString method to convert the current date-time value to a
                  string in terms of the master time zone.




46
                                                       CHAPTER 2   Querying and Updating Data in the Database




Example of Using Submitted Values in Search Filters
In a web page, the data submitted by the end user is accessible as string values. These strings can
be representations of date-time values, decimal values, numeric values, or other non string values.
The end user may have specified the value in a locale-dependent format (for example, the end user
may have used a comma as the decimal separator).

If you want to use these values in a search filter, you need to convert the value to a
locale-independent format. Use the ClarifyCRM Session.ToSQLString method to perform this
conversion. Since the submitted values are already strings, you need to identify the original data
type of the value by passing in the type as an argument.

For example, suppose that the end user submits a decimal value in the form element named
dec_value. In your web page, you get this value as a string representation. If you want to use this
value in a search filter, you can call the Session.ToSQLString method to convert this string
(formatted according to the end user’s locale) to a locale-independent string.
   In Java (in JSP):
   <%@ page import="com.clarify.cbo.*, com.clarify.common.*" %>
   <jsp:useBean id="cboWebSupStr" scope="session"
        class="com.clarify.common.CboWebSupStr" />
   ...
   String strDecimalValue = null;
   if (cboWebSupStr.getParameter(request, session, "dec_value") != null) {
        strDecimalValue = ClfySession.toSQLString(
           (String) cboWebSupStr.getParameter(request, session,
           "dec_value"), CboConstants.cboSQLQuote,
           CboConstants.cboTypeDecimal);
        BoGeneric.setFilter("decimal_field = " + strDecimalValue);
   }
   ...
   In JavaScript (in ASP):
   ...
   if (Request.QueryString("dec_value").Count) {
      var strDecimalValue = ClfySession.ToSQLString(
         Request.QueryString("dec_value"), cboSQLQuote, cboTypeDecimal);
      BoGeneric.Filter = "decimal_field = " + strDecimalValue;
   }
   ...

If the end user’s locale is standard French and the submitted value is 13,56, the ClarifyCRM
Session.ToSQLString method returns the string representation 13.56. The method replaces
the locale-specific decimal separator (a comma) with a period.


Specifying a Search Filter for Related Tables
If you want to specify fields in related database tables as part of your search filter, use the
BO.FilterRelated property. Set this property to an expression in the following format:
relation_path;filter

relation_path is a colon-delimited (:) list of the relations from the table in the current business
object to the table that has the field you want to use. Use a semicolon to separate the list of relations
from the search filter.


                                                                                                          47
Using Business Objects to Query the Database




                  The following examples set up a search filter to find all cases logged for sites that are located in
                  California.
                     In Java:
                     BoCase.setFilterRelated("case_reporter2site:cust_primaddr2address:
                        address2state_prov;name='CA'");
                     In JavaScript or Visual Basic:
                     BoCase.FilterRelated = "case_reporter2site:cust_primaddr2address:
                        address2state_prov;name='CA'"

                  These examples follow the relations from the case to the site reporting the case (via
                  case_reporter2site), from the site to its primary address (via cust_primaddr2address),
                  and from the primary address to its state/province (via address2state_prov). The field used in
                  the search filter (name) is delimited from the other relations by a (;).

                  If you want to specify a combination of filters, all of which must be true (logical AND), use a pair of
                  semicolons (“;;”) between filters:
                  path1;filter1;;path2;filter2...

                  The following examples set up a Case business object to search for cases that are closed and are for
                  sites in California:
                     In Java:
                     BoCase.setFilterRelated("case_reporter2site:cust_primaddr2address:
                        address2state_prov;name='CA';;case_state2condition;
                        title='Closed'");
                     In JavaScript or Visual Basic:
                     BoCase.FilterRelated = "case_reporter2site:cust_primaddr2address:
                        address2state_prov;name='CA';;case_state2condition;
                        title='Closed'"

                  This example narrows down the search results to find only closed cases. The example follows the
                  relations from the case to the condition (via case_state2condition) and uses the title field
                  from the condition to determine if the case is closed.

                  If you want to specify a combination of filters, any one of which must be true (logical OR), use a
                  pair of circumflexes (^^) between filters:
                  path1;filter1^^path2;filter2...

                  You can group ORed filters using curly braces ({ }).

                  The following examples set up a user object to search for an employee whose first name is Elena
                  who recently changed her last name from Hunt to Nelson:
                     In Java:
                     BoUser.setFilterRelated("user2employee;firstname= 'Elena';;
                     {user2employee;lastname='Hunt'^^user2employee;lastname= 'Nelson'}");
                     In JavaScript or Visual Basic:
                     BoUser.FilterRelated = "user2employee;firstname= 'Elena';;
                     {user2employee;lastname='Hunt'^^user2employee;lastname= 'Nelson'}"

                  These examples follow the relations from the user to the employee (via user2employee) using
                  the firstname field to find Elena and the lastname field to find Hunt or Nelson.


48
                                                          CHAPTER 2   Querying and Updating Data in the Database




If you specify the BO.Filter property in addition to the BO.FilterRelated property, the
business object uses both properties to find only the rows that match both filters. This includes
rows in a related table that represents a child object. For example, you can set the Filter property
for a Case object and then set the FilterRelated property for the PhoneLog child object:
boCase.setFilter("id_number = '4'");
boCase.getPhoneLog().setQueryMode(CboConstants.cboSubmitEnabled);
boCase.getPhoneLog().setFilterRelated("phone_custmr2contact;last_name =
'Gibbons'");

Combining the filters is like a logical AND; only rows where the last_name field is equal to
“Gibbons” are returned from the contact table.


Specifying Sort Order
To specify the sort order for the query results, set the BO.SortOrder property to a
comma-delimited list of the fields that you want used for sorting.

Note: If you are sorting by the objid field, do not assume that the order of object IDs corresponds to the
order in which the rows were inserted in the database. ClarifyCRM applications can cache a range of object
IDs for use when inserting new rows, return any unused object IDs, and use those previously unused object
IDs at a later time. As a result, objects IDs are not always used to insert rows in to the database at the time
when the object IDs are generated.


By default, the results are sorted by the specified fields in ascending order. For each field in the list,
you can specify an ascending or descending sort order by using the following syntax:
field1 [A|ASC|D|DESC],field2 [A|ASC|D|DESC]...

For example, the following section of code sets up a query to search for any contacts in the
database. The results are sorted first by last name in ascending order and then by first name in
descending order:
   In Java:
   BoGeneric.setDataFields("*");
   BoGeneric.setSortOrder("last_name ASC,first_name DESC");
   In JavaScript:
   BoGeneric.DataFields = "*"
   BoGeneric.SortOrder = "last_name ASC,first_name DESC"

To specify the sort order in terms of a related table, set the BO.SortOrderRelated property. Use
the following syntax for this property:
relation_path1;field1 [A|ASC|D|DESC];;
   relation_path2;field2 [A|ASC|D|DESC]...

relation_path is a colon-delimited (:) list of the relations from the table in the current business
object to the table that has the field you want to use. Use a semicolon to separate the list of relations
from the sort order specification.




                                                                                                             49
Using Business Objects to Query the Database




                  For example, the following examples set up a Case business object to sort the results by the first
                  and last name of the employee who owns the case.
                     In Java:
                     BoCase.setSortOrderRelated(
                        "case_owner2user:user2employee;first_name a;;
                        case_owner2user:user2employee;last_name a");
                     In JavaScript or Visual Basic:
                     BoCase.SortOrderRelated =
                        "case_owner2user:user2employee;first_name a;;
                        case_owner2user:user2employee;last_name a"

                  If you set both the BO.SortOrder and BO.SortOrderRelated properties, the business object
                  uses the BO.SortOrder property first to sort the results. (In the generated SQL statement, any
                  fields specified in the BO.SortOrder property appear first in the ORDER BY clause, followed by
                  any fields specified in the BO.SortOrderRelated property.)


                  Example of Setting Up Search Criteria
                  The following SQL statement queries the database for the names, email addresses, and phone
                  numbers of contacts with the last name Smith. The results are sorted first by last name, then by
                  first name.
                  select first_name, last_name, e_mail, phone
                  from table_contact
                  where last_name = 'Smith'
                  order by last_name, first_name

                  The following examples demonstrate how to generate this SQL statement from a Generic
                  business object:
                     In Java:
                     com.clarify.cbo.Generic boGeneric =
                          boContext.createGenericBO("contact");
                     boGeneric.setDataFields("first_name,last_name,e_mail,phone");
                     boGeneric.setFilter("last_name = 'Smith'");
                     boGeneric.setSortOrder("last_name,first_name");
                     In JavaScript:
                     BoGeneric = ClfyForm.CreateGenericBO("contact");
                     BoGeneric.DataFields = "first_name,last_name,e_mail,phone"
                     BoGeneric.Filter = "last_name = 'Smith'"
                     BoGeneric.SortOrder = "last_name,first_name"

                  (You can also use the Contact business object to perform the query. If you use the Contact
                  business object, omit the statement that sets the GenericBO.DBObjectName property.)




50
                                                          CHAPTER 2   Querying and Updating Data in the Database




The following example sets up a filter to find rows that have been modified since January 1, 1999:
   In Java:
   import com.clarify.cbo.*;
   import java.util.*;
   ...
   // Create the GregorianCalendar object.
   Calendar dateModified = new GregorianCalendar();
   dateModified.set(1999, Calendar.JANUARY, 1);
   // Set the query to retrieve all fields.
   BoGeneric.setDataFields("*");
   // Specify the filter.
   String strModDate = ClfySession.toSQLString(dateModified,
      CboConstants.cboSQLQuote, CboConstants.cboTypeDateTime);
   BoGeneric.setFilter("update_stamp > " + strModDate);
   // Specify the sort order of the search results.
   BoGeneric.setSortOrder("last_name,first_name");
   // Perform the query.
   ...
   In JavaScript:
   var d, vDate;
   // Create the Date object. (Months are counted from 0 for January.)
   d = new Date(1999, 0, 1);
   vDate = d.getVarDate();
   // Set the query to retrieve all fields.
   BoGeneric.DataFields = "*";
   // Specify the filter.
   BoGeneric.Filter = "update_stamp > " + ClfySession.ToSQLString(vDate);
   // Specify the sort order of the search results.
   BoGeneric.SortOrder = "last_name,first_name";
   // Perform the query.
   ...


Querying the Database
After you set up the search criteria for the business object, you can query the database by using one
of the following methods:
   To query the business object, use the BO.Query method.
   To query all business objects on a form, use the BoContext/FormContext Start method.
   To submit a single query for a group of business objects on a form, use one of the signatures of
   the BoContext.query method. For details on performing this type of bulk query, see
   Chapter 9, Using Global Transactions and Bulk Queries.

The business object finds and copies any matching rows from the database table to the rowset.

Note: The data in the rowset of the business object is a copy of the data in the database. Any changes that you
make to this data are not immediately committed to the database. For details on committing changes to the
database, see Committing Changes to the Database on page 83.


To see an example of script code that queries the database and retrieves results, see Example of
Querying the Database on page 62.


                                                                                                             51
Getting Data from the Rowset




Getting Data from the Rowset
                  To get data from a rowset, you can use the following techniques:
                      (Java only) You can get the data as an array of XVOs, where each XVO contains the data from
                      one of the rows in the rowset. From this array, you can get the values of specific fields by calling
                      the getter methods for those fields from the individual XVOs. For details, see Copying a Rowset
                      from a Business Object to an Array of XVOs on page 129 in Chapter 3, Working with XVOs.
                      (Java only) You can get the data from the currently selected row as an XVO, and you can get the
                      values of specific fields by calling the getter methods for those fields. For details, see Copying a
                      Row in a Business Object to an XVO on page 128 in Chapter 3, Working with XVOs.
                      You can use the Fields collection to get the fields in the currently selected row, get the Field
                      objects for specific fields in the collection, and get the values from those Field objects.

                  Unlike the XVOs, which have a getter method for each field in the row, the Fields collection has
                  indexed properties that require that you pass in the name or index of the field to retrieve.

                  Because of this difference, XVOs are easier to use and are the recommended interface in Java. For
                  example, if you are using the Field object, a typo in the field name causes a runtime error. By
                  comparison, in XVOs, the field names are part of the method names. If you incorrectly specify a
                  field name within the method name, the compiler catches the error when you compile your class.

                  For the latter two techniques, see Navigating Through the Rowset on page 52 for details on how to
                  select a row in the rowset and Accessing Data from the Currently Selected Row on page 55 for details
                  on how to get data from fields in the row.


                  Navigating Through the Rowset
                  In a business object’s rowset, one of the rows is always selected. (In this manual, this row is referred
                  to as the currently selected row.) When you select a row, you can access the values of fields in that
                  row. You can also replace or delete that row and establish relations between that row and other
                  rows in the database.

                  After you query the database, the business object automatically selects the first row in the rowset.
                  To navigate within the rowset and select a particular row, use the following methods and
                  properties (shown in Figure 7):
                      BO.Position property
                      This property specifies the 1-based index of the currently selected row in the rowset. You can get
                      this property to identify the currently selected row. You can also change the currently selected
                      row by setting this property to a different index in the rowset.
                      If the query returned no rows, the value of the BO.Position property is 0.
                      BO.MoveFirst, BO.MoveNext, BO.MovePrevious, BO.MoveLast, BO.MoveRelative
                      methods
                      These methods move the current position to the first, next, previous, and last row in the rowset
                      (respectively). The BO.MoveRelative method moves the current position relative to the
                      currently selected row (for example, three rows after the currently selected row).
                      You can use these methods to iterate through the rows in the rowset.




52
                                                          CHAPTER 2   Querying and Updating Data in the Database




   BO.BOF, BO.EOF properties
   These properties are set to True if you attempt to move the current position before the first row
   or after the last row in the rowset (respectively).
   If the query returned no rows, both BO.BOF and BO.EOF are set to True.
   BO.Count property
   This property specifies the number of rows that are currently exposed (rows that are accessible)
   in the rowset. (In business objects that have parent-child relationships, not all the rows in a
   rowset may be exposed. For details, see Chapter 4, Working with Database Relations.)
   You can use this property as the upper limit when iterating through the rows.
   If the query returned no rows, the value of the BO.Count property is 0.
   BO.Find method
   To find a row with a specific field value, call this method and pass the field name and value. If a
   matching row is found, the business object sets the current position to that row, and the method
   returns True. Use the following syntax for this method:
   – In Java:
      BO.find(fieldName, fieldValue, CboConstants.enumType)
   – In JavaScript or Visual Basic:
      BO.Find(fieldName, fieldValue, enumType)
   For enumType, you can pass the constant cboFindFirst (to find the first matching row in the
   rowset) or cboFindNext (to find the next matching row from the current position). In Java,
   these constants are defined in the CboConstants class.

Figure 7   How the currently selected row affects the properties and methods

 Currently selected row

                      BoCase (a Case business object)        The BoCase.MoveFirst method
                                                             selects the first row in the set.
                id_number         title             ...
                3                 Problem           ...
                                                             The BoCase.MovePrevious method
                56                S/W upgrade       ...      selects the previous row.
                92                Upgrade info      ...
                115               Need upgrade      ...      The BoCase.MoveNext method
                                                             selects the next row.
                214               Ship patch rel.   ...

                                Rowset                       The BoCase.MoveLast method
                                                             selects the last row.

   BoCase.Count: 5 (identifies the number of exposed rows)
   BoCase.Position: 3 (identifies currently selected row)




                                                                                                             53
Getting Data from the Rowset




                  The following section of code uses these methods to loop through each row in the rowset.
                      In Java:
                      ...
                      // Loop through each row in the rowset.
                      for (int nRows = 1; nRows <= BoSite.getCount(); nRows++) {
                           ... Perform some task on each row. ...
                           // Move to the next row in the rowset.
                           BoSite.moveNext();
                      }
                      ...
                      In JavaScript:
                      ...
                      // Loop through each row in the rowset.
                      for (nRows = 1; nRows <= BoSite.Count; nRows++) {
                           ... Perform some task on each row. ...
                           // Move to the next row in the rowset.
                           BoSite.MoveNext();
                      }
                      ...

                  The following section of code uses the BO.Find method to find all internal sites (sites of type 2) in
                  a rowset for the Site business object:
                      In Java:
                      import com.clarify.cbo.*;
                      ...
                      boolean bFoundMatch;
                      // Find the first internal site in the rowset.
                      bFoundMatch = BoSite.find("type", 2, CboConstants.cboFindFirst);
                      while (bFoundMatch) {
                         ...
                         // Perform some task on each row.
                         ...
                         // Find the next internal site.
                         bFoundMatch = BoSite.find("type", 2, CboConstants.cboFindNext);
                      }
                         ...
                      In JavaScript:
                      ...
                      // Find the first internal site in the rowset.
                      var bFoundMatch = BoSite.Find("type", 2, cboFindFirst);
                      while (bFoundMatch) {
                         ...
                         // Perform some task on each row.
                         ...
                         // Find the next internal site.
                         bFoundMatch = BoSite.Find("type", 2, cboFindNext);
                      }
                         ...

                  Figure 8 illustrates how the methods and properties are affected if the search turns up no rows.




54
                                                         CHAPTER 2   Querying and Updating Data in the Database




Figure 8   How the properties and methods work if no rows are returned

   No rows found
                      BoCase (a Case business object)         The BoCase.MoveXXX methods
                                                              have no effect on the rowset.
                                                              Setting the BoCase.Position
                id_number        title             ...        property has no effect on the rowset.


                                Rowset

    BoCase.Count: 0 (identifies the number of exposed rows)
    BoCase.Position: 0 (identifies currently selected row)
    BoCase.BOF: True
    BoCase.EOF: True



Accessing Data from the Currently Selected Row
To get data from the currently selected row, you can use one of the following techniques:
   (Java only) You can get the data from the currently selected row as an XVO, and you can get the
   values of specific fields by calling the getter methods for those fields. For details, see Copying a
   Row in a Business Object to an XVO on page 128 and Getting Field Values from an XVO on page 131
   under Chapter 3, Working with XVOs.
   You can use the Fields collection to get the fields in the currently selected row, get the Field
   objects for specific fields in the collection, and get the values from those Field objects. For
   details, see Using the Field Object to Get Data from the Row on page 55.


Using the Field Object to Get Data from the Row
Another way to get data from the currently selected row is to use the Fields collection and the
Field object. The Fields collection contains Field objects that represent the database fields in
the rowset.

Using this technique, to get a specific field value, you identify the field by specifying the name or
ID of the field. (By contrast, XVOs have specific getter methods for each field in the row.)

To get a field in the currently selected row, you can use the BO.Fields property, which is a
reference to a Fields collection. From this Fields collections, you can get the number of Field
objects from the Fields.Count property. You can also get individual Field objects from the
Fields collection in one of the following ways:
   Getting Field Objects by Name and Index on page 56
   Getting Field Objects by Generic Field ID on page 56
   Getting Field Objects by Enumeration on page 57




                                                                                                            55
Getting Data from the Rowset




                  Some fields in the database implement relations between tables. (For example, the case table has a
                  field named case_reporter2contact, which implements the database relation between the
                  case and contact tables.) You can use the Fields collection to access these types of fields.

                  Note: Although you can get the values of this type of field through the Fields.Item property, you cannot
                  set the values of this type of fields. (These fields are read-only.) If you want to establish or remove database
                  relations between rows in tables, see Chapter 4, Working with Database Relations.


                  Getting Field Objects by Name and Index
                  To get the Field object for a field by its name or by its index (the index number is 1-based), use the
                  Fields.Item property.

                  For example, the following statements get Field objects from the Fields collection. The first
                  statement gets the Field object for the id_number field of the currently selected row, and the
                  second statement gets the first Field object in the collection for the currently selected row.
                      In Java:
                      import com.clarify.cbo.*;
                      ...
                      Field CaseIdFieldObj = BoCase.getFields().getItem("id_number");
                      Field FirstFieldObj = BoCase.getFields().getItem(1);
                      In JavaScript:
                      CaseIdFieldObj = BoCase.Fields.Item("id_number")
                      FirstFieldObj = BoCase.Fields.Item(1)

                  Getting Field Objects by Generic Field ID
                  You can also access the Field object for a field by its generic field ID. (The ClarifyCRM client uses
                  generic field IDs to identify fields in different tables that are handled in the same way. For more
                  information on generic field IDs, see the Data Dictionary Guide.) To get a field by its generic field ID,
                  get the Fields.ItemByGenericID property.

                  Note: Any field that is not explicitly assigned a generic ID in the ClarifyCRM data dictionary has the
                  generic ID of -1.


                  The following example gets the Field object for the field with the generic ID 4:
                      In Java:
                      import com.clarify.cbo.*;
                      ...
                      Case BoCase;
                      ...
                      Field GenIdFieldObj = BoCase.getFields().getItemByGenericId(4);
                      In JavaScript:
                      GenIdFieldObj = BoCase.Fields.ItemByGenericId(4)




56
                                                      CHAPTER 2   Querying and Updating Data in the Database




Getting Field Objects by Enumeration
In Java, because the Fields object implements the Enumeration interface, you can use the
methods in this interface to retrieve Field objects:
   To get the next Field object in the enumeration, use the Fields.nextElement method.
   Because this method returns a value of the Object class, you must explicitly cast the return
   value as a Field object.
   To determine if there are any more Field objects left in the enumeration, use the
   Fields.hasMoreElements method.

In addition, if you need to reset the enumeration to the first element, use the Fields.elements
method. (You can also use this method to return another reference to the enumeration of fields.)

The following example in Java demonstrates how to use these methods to retrieve all Field
objects from the enumeration in a Fields object:
import com.clarify.cbo.*;
...
Fields enumFields = null;
Field tempField = null;
Generic boGeneric = null;
...
// Query the database.
boGeneric.query();
// Get the enumeration of Field objects.
enumFields = boGeneric.getFields();
// Iterate through each Field object in the enumeration.
while (enumFields.hasMoreElements()) {
   // Get the next Field object.
   tempField = (Field) enumFields.nextElement();
   System.out.println(tempField.getName() + ": " +
      tempField.getValue());
}
...
// If you need to iterate through the enumeration again,
// reset the enumeration to the first Field object.
enumFields.elements();
// Now, you can iterate through the enumeration again.
while (enumFields.hasMoreElements()) {
   tempField = (Field) enumFields.nextElement();
   ...
}

Getting the Value from a Field Object
For a given Field object, you can get the value of a database field through the following methods
and properties:
   To get the value of a field, get the Field.Value property.
   If you want to get the string representation of the value of a nonstring field, use the
   Field.ToString method.




                                                                                                         57
Getting Data from the Rowset




                      If you want to present a nonstring field as an HTML string, you can use Bo.ToHtmlString or
                      Bo.ToHtmlStaticString methods. The ToHtmlStaticString method is the same as the
                      ToHtmlString method except that it returns “&nbsp;” instead of empty strings to resolve
                      display issues with certain browsers.

                  Note: If you want to present a nonstring value to an end user, always use the Bo.ToString method to get
                  the string representation of the value. This method takes the end user’s locale and time zone in to account
                  when converting values to strings. See Working with Field Values and Data Types on page 92 for details.


                  Before you attempt to get the value of a field, you should check whether the field is empty or
                  whether the field has a value. For details, see Determining If the Field Object Is Empty on page 58.

                  The following sections explain how to get field values in different languages:
                      Getting the Value of a Field Object in Java on page 59
                      Getting Field Values in ActiveX (Visual Basic, VBScript, JavaScript) on page 59

                  Determining If the Field Object Is Empty
                  CBO distinguishes fields that have not been initialized by marking them as “empty”.

                  If you did not retrieve the value for a specific field when querying, that field is uninitialized. This is
                  the case, for example, if you do not include the field name when setting the BO.DataFields
                  property before a query.

                  In ActiveX, the Field.Value property for an empty field contains the Variant Empty value. In
                  Java, the Field.Value property for an empty field contains the default value for that field’s data
                  type. Before attempting to get the value of a field, you can determine if the field is empty by using
                  one of the following methods:
                      To determine if a field in the currently selected row is empty, use the Field.IsEmpty method.
                      To determine if a field in a different row is empty, use the Field.IsEmptyAt method, and
                      pass in the position of that row as an argument to this method.

                  The following example prints out the title field or the string "Empty" if the field holds no value.
                  The example does this for the currently selected row and for the last row in the rowset.
                      In Java:
                      ...
                      if (boGeneric.getFields().getItem("title").isEmpty()) {
                         System.out.println("Current Row: Empty");
                      } else {
                         System.out.println("Current Row: " +
                            boGeneric.getFields().getItem("title").getValue());
                      }
                      if (boGeneric.getFields().getItem("title").isEmptyAt(
                         boGeneric.getCount())) {
                         System.out.println("Last Row: Empty");
                      } else {
                         System.out.println("Last Row: " +
                            boGeneric.getFields().getItem("title").getValueAt(
                            boGeneric.getCount()));
                      }
                      ...



58
                                                      CHAPTER 2   Querying and Updating Data in the Database




   In JavaScript:
   ...
   if (boGeneric.Fields.Item("title").isEmpty()) {
      WScript.Echo("Current Row: Empty");
   } else {
      WScript.Echo("Current Row: " +
         boGeneric.Fields.Item("title").Value);
   }
   if (boGeneric.Fields.Item("title").IsEmptyAt(boGeneric.Count)) {
      WScript.Echo("Last Row: Empty");
   } else {
      WScript.Echo("Last Row: " +
         boGeneric.Fields.Item("title").ValueAt(boGeneric.Count));
      }
   ...

Getting the Value of a Field Object in Java
In Java, the business objects have a BO.getValue method to get the value of a Field object. This
method is shorthand for specifying the getFields().getItem() methods:
Class MyVar = (Class) BO.getValue("Field_Name");

The full syntax for getting the Field.Value property is:
Class MyVar = (Class) BO.getFields().getItem("Name").getValue();

The Field.getValue method returns an Object. You must cast the returned Object as the
appropriate class:
   If the database field has the type varchar, cast the returned object as String. You can also use
   the Field.toString method to get the value of the field.
   If the database field has the types int or datetime, cast the returned object as Integer or
   java.util.Calendar (respectively).

For example, the following statements get the value of an integer field:
Integer myVar = (Integer) BO.getFields().getItem("Name").getValue();
Integer myVar2 = (Integer) BO.getValue("Name");

The following statements get the value of a date-time field:
import java.util.*;
...
Calendar myVar = (Calendar) BO.getFields().getItem("Name").getValue();
Calendar myVar2 = (Calendar) BO.getValue("Name");

The following statements get the value of a string field:
String myVar = BO.toString("name");
String myVar1 = BO.getFields().getItem("Name").getValue().toString();
String myVar2 = BO.getValue("Name").toString();

Getting Field Values in ActiveX (Visual Basic, VBScript, JavaScript)
In ActiveX, the complete syntax for getting the Field.Value property is:
MyVar = BO.Fields.Item("Name").Value




                                                                                                         59
Getting Data from the Rowset




                     Because BO.Fields is the default property of the business object and Fields.Item is the default
                     property of the Fields collection, you can omit these property names and shorten this expression
                     to:
                     MyVar = BO("Name").Value

                     In Visual Basic or VBScript, you can also omit the Value property, since Field.Value is the
                     default property of the Field object:
                     'Visual Basic/VBScript code to get the value of the field
                     MyVar = BO("Name")

                     In JavaScript, omitting the Value property sets the variable to an object reference. (This is
                     equivalent to using the Set statement in Visual Basic or VBScript.) If you want to get the value of a
                     field, you must specify the Value property:
                     // JavaScript code to get the value of the field
                     MyVar = BOSite("name").Value;

                     Figure 9 shows the difference between getting a reference to the Field object and getting the value
                     of the field.

Figure 9         The distinction between getting the Field object and getting the Field.Value property

Currently selected row                                                     Field object for the title field in the case table

                                                                           To get this object reference in Visual Basic or VBScript:
                 BoCase (a Case business object)                           Dim myVar
                                                                           Set myVar = BoCase("title")
           id_number           title              ...                      To get this object reference in JavaScript:
           3                   Problem            ...                      var myVar = BoCase("title");

           56                  S/W upgrade        ...
           92                  Upgrade info       ...
                                                                           Value of the title field in the currently selected row
           115                 Need upgrade       ...
           214                 Ship patch rel.    ...                      To get this value in Visual Basic or VBScript:
                                                                           Dim myVar
                                                                           myVar = BoCase("title")
                             Rowset
                                                                           To get this value in JavaScript:
                                                                           var myVar = BoCase("title").Value;



                     Use caution when setting a variable to an object reference instead of a field value. If you change the
                     currently selected row, the variable reflects the field value in the newly selected row. See Figure 10
                     for an example of how this may affect your script code.




60
                                                                             CHAPTER 2   Querying and Updating Data in the Database




Figure 10   How Field object references are affected by the currently selected row

  Currently selected row
                                                                          // This gets a Field object reference.
                                                                          var myVar = BoCase("title");
                        BoCase (a Case business object)                   // This statement prints the value Upgrade Info.
                                                                          WScript.Echo(myVar);
                                                                          // Move to the next row.
                   id_number           title               ...            BoCase.MoveNext();
                   3                   Problem             ...            // This statement prints the value Need upgrade.
                                                                          WScript.Echo(myVar);
                   56                  S/W upgrade         ...            // Since myVar is an object reference, changing
                                                                          // currently selected row changes the field value that
                   92                  Upgrade info        ...            // you get when comparing or printing the variable.
                   115                 Need upgrade        ...
                   214                 Ship patch rel.     ...

                                    Rowset


                If you get the value of a field that contains a foreign key (for example, the primary2bus_org field
                in the site table), the object ID in that field is returned as a string.

                If the relation points to a dummy row (a row with the object ID -2) or if the relation is set to null, the
                getValue method in Java returns a String object that contains an empty string. (In ActiveX, the
                Value property is set to null for relations that are set to the dummy row or null.)

                Note: In Java, if there are no rows in the business object and you call the getValue method for a field
                representing a relation, the method returns a java.lang.Integer object containing the value 0.


                Getting Information About a Field
                Table 3 lists some of the other information that you can get about a field.
                Table 3     Getting information about a field
                 Type of Information                                        Field Property for Getting That Information
                 Physical name of the field                                 Field.Name
                 Logical name of the field                                  Field.LogicalName
                 Data type of the field                                     Field.Type (see the Core Business Objects Reference
                                                                            Guide for a listing of data types returned by this
                                                                            property)
                 Size of the field in bytes (for varchar fields, this       Field.DefinedSize
                 corresponds to the array size of the field in the data
                 dictionary)
                 Generic ID of the field                                    Field.GenericId
                 Precision of the field (for decimal fields)                Field.Precision
                 Scale of the field (for decimal fields)                    Field.Scale
                 Index of field within the Fields collection                Field.Ordinal
                 Type of field (Is the field a foreign key field)           Field.IsForeignKey




                                                                                                                                   61
Example of Querying the Database




                 Getting the Object ID of a Row
                 To get the object ID of the currently selected row, use the BO.Id property. This property returns the
                 object ID as a string value. For example, in Java, call the getId method:
                     // Getting the object ID in Java
                     String strObjid = myBO.getId();

                 Note: In Java, if you need to compare object IDs, make sure to use the String.equals method instead of
                 the == operator. (The == operator determines if the two variables are references to the same String object.
                 This operator does not compare the actual string values of the two variables.)


                 If you get the BO.Id property for a row that you have just added to the rowset (a row that has not
                 been committed to the database yet), the property returns a temporary ID. The permanent object ID
                 is assigned when the row is committed to the database. (For information on adding rows to the
                 rowset, see the section Adding New Rows in Database Tables on page 69.)

                 Note: In Java, if you call the BO.getId method and the business object contains no rows (in other words, if
                 the rowset is empty), the method returns an empty String object.


                 When working with the value of the BO.Id property, make sure to keep the value as a string. Do
                 not attempt to convert the value to a Long type.

                 If you are using an XVO to get data from the business object, see Getting the Object ID from an XVO
                 for Table on page 132 in Chapter 3, Working with XVOs, for details on getting the object ID from the
                 XVO.




Example of Querying the Database
                 The following section is an example of Java code that performs a search and prints out the results.
                 The example finds each site in the database and displays the object ID, site ID, site name, and the
                 date when the site record was last modified.

                 The example demonstrates two techniques for getting the search results from the rowset: using
                 XVOs and using shortcut methods to the Field object.

                 Note: For both techniques, because the values are being displayed, the example uses the CBO toString
                 methods to get the string representation of the date value. For details about the importance of using these
                 methods to get string representations, see Working with Field Values and Data Types on page 92.

                 ...
                 // Create a Generic business object to search for all sites.
                 Generic genSiteBo = boContext.createGenericBO("site");
                 // Set up the object to retrieve all fields.
                 genSiteBo.setDataFields("*");
                 // Sort the results by site ID.
                 genSiteBo.setSortOrder("site_id");
                 // Query the database.
                 genSiteBo.query();




62
                                                  CHAPTER 2   Querying and Updating Data in the Database




// You can use one of the following ways to get data from the rowset.

// Technique #1: Use XVOs to get the data.
// Get an array of XVOs for the sites in the search results.
Xvo[] arrayVo = genSiteBo.getVoAll();
for (int n = 0; n < arrayVo.length; n++) {
   // Cast the XVO as SiteVo to use getter methods for this class.
   SiteVo siteVo = (SiteVo)arrayVo[n];
   // Get and print the object ID, site ID, site name, and update time.
   System.out.println(siteVo.getObjid() + ", " +
      siteVo.getSiteId() + ", " + siteVo.getName() + ", " +
      cboSession.toDateString(siteVo.getUpdateStamp()));
}

// Move back to the start of the rowset to demonstrate the other technique.
genSiteBo.moveFirst();

// Technique #2: Use shortcuts to the getValue and toString methods
// of the Field object to get the data.
for (int n = 1; n < genSiteBo.getCount(); n++) {
   // Get and print the object ID, site ID, site name, and update time.
   System.out.println(genSiteBo.getId() + ", " +
      genSiteBo.getValue("site_id") + ", " +
      genSiteBo.getValue("name") + ", " +
      genSiteBo.toString("update_stamp"));
   // Move to the next row in the rowset.
   genSiteBo.moveNext();
}

The second technique above uses the BO.Count property iterate through the results. The following
section of Java code demonstrates how to use the BO.EOF property in a while loop.
...
while (!genSiteBo.getEOF()) {
   // Get and print the object ID, site ID, site name, and update time.
   System.out.println(genSiteBo.getId() + ", " +
      genSiteBo.getValue("site_id") + ", " +
      genSiteBo.getValue("name") + ", " +
      genSiteBo.toString("update_stamp"));
   // Move to the next row in the rowset.
   genSiteBo.moveNext();
}
...




                                                                                                     63
Example of Querying the Database




                 The following section is an example of an ASP page that contains JavaScript code. The code finds
                 each site in the database and displays the object ID, site ID, site name, and the date when the site
                 record was last modified.
                 ...
                 <%
                 var BoSite, nRows
                 // Create a generic business object.
                 BoSite = ClfyForm.CreateGenericBO("site")
                 // Set up the object to find all sites.
                 BoSite.DataFields = "*"
                 BoSite.SortOrder = "site_id"
                 // Query the database.
                 BoSite.Query()
                 %>
                 <HTML>
                 <HEAD>
                 <TITLE>Sites in Database</TITLE>
                 </HEAD>
                 <BODY>
                 <!-- Display the number of sites found. -->
                 <P>Sites Found: <%=BoSite.Count%></P>
                 <TABLE BORDER=1>
                 <TR>
                    <TH>objid</TH>
                    <TH>site_id</TH>
                    <TH>name</TH>
                    <TH>update_stamp</TH>
                 </TR>
                 <%
                    // Iterate through each row in the results.
                    // Get the object ID, the site ID, and the name.
                    for (nRows = 1; nRows < BoSite.Count + 1; nRows++) {
                       %>
                       <TR>
                          <TD><%=BoSite.Id%>&nbsp;</TD>
                          <TD><%=BoSite("site_id").Value%>&nbsp;</TD>
                          <TD><%=BoSite("name").Value%>&nbsp;</TD>
                          // Use the Field.ToString method to display the date-time
                          // value in the correct format and time zone for the end user.
                          <TD><%=BoSite("update_stamp").ToString()%>&nbsp;</TD>
                       </TR>
                       <%
                       // Move to the next row in the results.
                       BoSite.MoveNext()
                    }
                 %>
                 </TABLE>
                 </BODY>
                 </HTML>




64
                                                               CHAPTER 2   Querying and Updating Data in the Database




        The preceding example uses the BO.Count property to iterate through the results. The following
        section of JavaScript code demonstrates how to use the BO.EOF property in a while loop.
        ...
        // Iterate through each row in the results.
        // Get the object ID, the site ID, and the name.
        while (! BoSite.EOF) {
           %>
           <TR>
              <TD><%=BoSite.Id%>&nbsp;</TD>
              <TD><%=BoSite("site_id")%>&nbsp;</TD>
              <TD><%=BoSite("name")%>&nbsp;</TD>
           </TR>
           <%
           // Move to the next row in the results.
           BoSite.MoveNext()
        }




Paging Search Results
        By default, when you perform a query, the business object returns all matching rows in the
        database. If you do not want to keep the data for all of these rows in memory, you can set up the
        query to return the results in pages.

        For example, suppose that you want to query the database to get a list of sites to display in a list
        box for the end user. The site table may contain thousands of sites. Instead of getting the data for all
        the sites at one time, you can get a page of results for the first few sites and present that data to the
        user. As the user scrolls through the list of sites, you can requery the database to get the next page
        of results for additional sites to display to the user.

        In the business objects, you can set this up by first querying only for object IDs. Then, in
        subsequent queries, you can specify which rows you want to retrieve data from, and the business
        object uses the object IDs of those rows to retrieve the data for those rows.

        In your application code, do the following:
        1. Set the BO.QueryMode property to cboSubmitQueryIDsOnly (in Java, use
           CboConstants.cboSubmitQueryIDsOnly) to set up the business object to retrieve object
           IDs only (no other data).
        2. Query the database (for example, by calling the BO.Query method).
        3. Set up the business object to query for data from a subset of rows by setting the BO.QueryMode
           property to cboSubmitRequery (in Java, use CboConstants.cboSubmitRequery).
        4. Set the BO.RequeryFirst and BO.RequeryLast properties to the positions of the first and
           last row that you want to retrieve data from. (1 indicates the first row in the rowset.)
        5. Query the database to get the data for those rows.
           The business object uses the objects IDs in the rowset and the BO.RequeryFirst and
           BO.RequeryLast properties to retrieve data for the specified subset of rows.
           After retrieving the data, the business object sets the currently selected row to the row specified
           by the BO.RequeryFirst property.




                                                                                                                  65
Paging Search Results




                  6. To query for other subsets of rows, change the values of the BO.RequeryFirst and
                     BO.RequeryLast properties, and query the database again.
                        When you query the database again, the business object clears out all data from previous
                        queries (except the object IDs). To determine if a row contains any data, you can use the
                        Field.IsEmpty method.

                        Note: Each subsequent query does not change the count of rows in the rowset. The query still returns the
                        same number of rows from the database. However, the query returns data only in the rows that fall
                        within the range specified by the BO.RequeryFirst and BO.RequeryLast properties. All other
                        rows contain only the object IDs and no other data.

                        Any new rows in the rowset (rows not yet committed to the database) are not affected by the
                        subsequent queries.

                 For example, the following code queries the database for the data in a subset of rows:
                        In Java:
                        int nPageSize = 100;
                        // First, get only the object IDs from the database.
                        boGeneric.setQueryMode(CboConstants.cboSubmitQueryIDsOnly);
                        boGeneric.query();
                        // Next, get the first page of data.
                        boGeneric.setQueryMode(CboConstants.cboSubmitRequery);
                        boGeneric.setRequeryLast(bo.getRequeryFirst() + nPageSize);
                        boGeneric.query();
                        ...
                        // Get the next page of data.
                        boGeneric.setRequeryFirst(bo.getRequeryLast() + 1);
                        boGeneric.setRequeryLast(bo.getRequeryFirst() + nPageSize);
                        boGeneric.query();
                        In JavaScript:
                        var cboSubmitRequery = 3;
                        var cboSubmitQueryIDsOnly = 5;
                        var nPageSize = 100;
                        // First, get only the object IDs from the database.
                        boGeneric.QueryMode = cboSubmitQueryIDsOnly;
                        boGeneric.Query();
                        // Next, get the first page of data.
                        boGeneric.QueryMode = cboSubmitRequery;
                        boGeneric.RequeryLast = bo.RequeryFirst + nPageSize;
                        boGeneric.Query();
                        ...
                        // Get the next page of data.
                        boGeneric.RequeryFirst = bo.RequeryLast + 1;
                        boGeneric.RequeryLast = bo.RequeryFirst + nPageSize;
                        boGeneric.Query();
                        ...




66
                                                       CHAPTER 2   Querying and Updating Data in the Database




When setting the BO.RequeryFirst and BO.RequeryLast properties, note the following:
   If you set the BO.RequeryFirst property to a value greater than the BO.RequeryLast
   property and you query the database, an error occurs.
   If you set the BO.RequeryLast property to a value greater than the count of rows in the
   rowset, the business object just retrieves data for the rows from the BO.RequeryFirst
   property to the end of the rowset.
   If you do not set the BO.RequeryFirst and BO.RequeryLast properties, the query returns
   data for all the rows.

As you requery the database, the business object clears all data from previous queries. If you want
to keep this data in the rowset, set the BO.QueryMode property to cboSubmitRequeryNoClear
instead of cboSubmitRequery.

For example, the following code queries for data from a subset of rows and retains the data from
the previous query:
   In Java:
   int nPageSize = 100;
   // Retain the data from previous queries.
   boGeneric.setQueryMode(CboConstants.cboSubmitRequeryNoClear);
   ...
   // Get the next page of data.
   boGeneric.setRequeryFirst(bo.getRequeryLast() + 1);
   boGeneric.setRequeryLast(bo.getRequeryFirst() + nPageSize);
   boGeneric.query();
   In JavaScript:
   var cboSubmitRequeryNoClear = 4;
   var nPageSize = 100;
   // Retain the data from previous queries.
   boGeneric.QueryMode = cboSubmitRequeryNoClear;
   ...
   // Get the next page of data.
   boGeneric.RequeryFirst = bo.RequeryLast + 1;
   boGeneric.RequeryLast = bo.RequeryFirst + nPageSize;
   boGeneric.Query();

If you want to selectively clear data from specific fields and rows when requerying the database,
use the BO.ClearFields method. When calling this method, pass in a comma-delimited list of
the fields that you want to clear and the positions of the first and last row that you want to clear. (1
indicates the first row in the rowset.)

For example, the following Java code clears data from the site_id and name fields in the
previous 100 rows:
int nPosition = boGeneric.getPosition();
int nClearStart = nPosition - 100;
int nClearEnd = nPosition - 1;
String strFieldsCleared = "site_id, name";
...
boGeneric.clearFields(strFieldsCleared, nClearStart, nClearEnd);

To clear data from all fields, specify * instead of a comma-delimited list of fields:
String strFieldsCleared = "*";
boGeneric.clearFields(strFieldsCleared, nClearStart, nClearEnd);


                                                                                                          67
Using Business Objects to Save Data to the Database




                  If you want to clear data from all rows in the rowset, you can omit the last two arguments. For
                  example:
                  boGeneric.clearFields(strFieldsCleared);

                  The following JavaScript code clears data from the site_id and name fields in the previous 100
                  rows:
                  var nPosition = boGeneric.Position;
                  var nClearStart = nPosition - 100;
                  var nClearEnd = nPosition - 1;
                  var strFieldsCleared = "site_id, name";
                  ...
                  boGeneric.ClearFields(strFieldsCleared, nClearStart, nClearEnd);




Using Business Objects to Save Data to the Database
                  Using the business object methods and properties, you can add new rows, modify existing rows,
                  change field values in any row, and delete rows in the business object’s rowset in memory. After
                  you finish making changes to the rowset, you can commit these changes to the database.

                  Note: You can also use business objects to establish or remove relations to the row. For details on modifying
                  relations to other rows, see Chapter 4, Working with Database Relations.


                  When using business objects to update data in the database, use the following guidelines:
                      If a specific business object is available for a database table, use that business object (rather than
                      a Generic business object) to add rows to that table. For example, do not use the Generic
                      business object to add a case to the database; use the Case business object.
                      If no specific business object is available and you need to use a Generic business object (for
                      example, if you are adding a row to the r_rqst table to generate a request for the Routing
                      Server), make sure you set all the required fields and relations in the row. You also need to
                      understand what other related data needs to be added or modified.
                      If you want to use a log business object (such as a NotesLog or a StatusChg business object)
                      to record information, activities, or changes, make sure that the business object is related to the
                      object that you are recording the information about. For example, when using a NotesLog
                      business object to log notes against a case, make sure that this business object is related to the
                      business object for a case. (See Chapter 4, Working with Database Relations for more information
                      on setting up relationships between business objects.)
                      Do not use a log business object to add a new row if the business object is not related to (or
                      contained within) any other business object.
                      If you are modifying a workflow item (such as a case, order, or dialogue), you do not need to be
                      logged in as the owner of the item in order to save changes. For example, you can change the
                      title of a case or close a case if you are logged in as a user who does not own the case. If you
                      want to prevent nonowners from modifying workflow items, your script code should compare
                      the login of the current user against the login of the workflow item owner before the user makes
                      or commits any changes.




68
                                                                 CHAPTER 2   Querying and Updating Data in the Database




        As is the case with getting data from a rowset, when adding or updating data in the rowset, you
        can use a number of different techniques:
           (Java only) You can set up the data in an XVO by calling the setter methods for specific fields,
           and then you can pass the data from the XVO to the business object by calling the BO.addVo
           method. For details, see Chapter 3, Working with XVOs.
           You can use the Fields collection to get the fields in the currently selected row, get the Field
           objects for specific fields in the collection, and set the values in those Field objects.

        As mentioned in Getting Data from the Rowset on page 52, XVOs have different methods for setting
        the value of different fields in the row, while the Fields collection has indexed properties that
        require that you pass in the name or index of the field to set. Because of this difference, XVOs are
        easier to use and are the recommended interface in Java.

        The next sections explain how to use the rowset in the business object to update data. For an
        example of using the business objects to modify data in the database, see Example of Modifying
        Contact Information on page 85.

        Note: If you want to explicitly specify a SQL statement to execute against the database (instead of using the
        business objects to generate a SQL statement), you can use the SqlExec object. Note that you should not
        use this object to update data in any of the standard ClarifyCRM tables and views. For details, see Querying
        and Updating Data Directly with SQL on page 86.




Adding New Rows in Database Tables
        To add a new row to a table in the database, you can use one of the following techniques:
           (Java only) You can use XVOs to specify the new rows to be added to the database.
           This solution applies to situations in which the business objects are deployed on a server (for
           example, by an EJB) and a separate client application (such as an EJB client) is submitting a
           request to add a new row. For details, see Using an XVO to Add a New Row on page 144.
           Add a new row to the business object, and use the Field object to set values in the fields in the
           new row, and use the business object to commit your changes to the database.

        To use the Field object to add new rows to a database table, do the following:
        1. Call the BO.AddNew method to create an empty new row in memory.
           If you want to start from a copy of an existing row (rather than fill in the fields of an empty
           row), use the BO.Duplicate method to add the row instead. (See Adding a Row by Duplicating
           an Existing Row on page 73.)
        2. Set the values of fields in the new row and establish relations to other rows. (See Setting Field
           Values on page 78.)




                                                                                                                    69
Adding New Rows in Database Tables




                 3. If you are using a Generic business object for a table uses a numbering scheme for IDs, set the
                    Field.NumSchemeName property of the ID field to the name of the numbering scheme. For
                    details, see Setting the Numbering Scheme on page 75.
                 4. Call the BO.Update method to commit the row to the database. For details, see Committing
                    Changes to the Database on page 83.

                 See the section Adding a New Row to the Rowset on page 70 for more details on these steps. For an
                 example of adding a new row, see Example of Adding a New Row on page 72.


                 Adding a New Row to the Rowset
                 To add a new row to the rowset, use the BO.AddNew method. This method adds a new row to the
                 rowset in memory, sets the current position to the new row, and fills the fields with their default
                 values. (For a discussion of how the default values for fields are determined, see the System
                 Administration Guide.)

                 Note: If you are using XVOs to add a new row (as explained in Using an XVO to Add a New Row on
                 page 144), you do not need to call this method. Passing an XVO for a new row to the BO.addVo method
                 effectively performs an addNew on the business object.


                 In the new row, you can set values for the fields (unless you are using an XVO to set the fields, as
                 explained in Using an XVO to Add a New Row on page 144), and you can establish relations to other
                 business objects. (See Setting Field Values on page 78 and Chapter 4, Working with Database Relations
                 for more information.) Make sure to set any required fields and mandatory relations. For a listing
                 of the required fields and relations, see the Data Dictionary for Objects.

                 By default, the BO.AddNew method generates a temporary object ID for the new row. This
                 temporary object ID uses the format ****NEW****<n>. When you call a method to update the
                 business object or BoContext object (see Committing Changes to the Database on page 83), the
                 business object assigns a real object ID to the new row and gets the new object ID of the row.

                 Note: You can change this so that the BO.AddNew method generates a real object ID for the new row and
                 assigns new ID numbers for fields that use numbering schemes. For details, see Generating the Object ID
                 and ID Number for a New Row on page 71.


                 In addition to the mandatory relations outlined in the documentation, the schema file also specifies
                 the required attribute for certain relations. (Note that required relations are not the same as the
                 mandatory relations listed in the documentation.)

                 The required attribute indicates that the relation must point to an existing object. For example:
                    <relation
                           name = "primary2bus_org"
                           relation_type = "MTO"
                           target_table = "bus_org"
                           inverse_relation = "primary_org2site"
                           required = "true"
                    >
                    <comment><![CDATA[Organization which owns the site]]></comment>



70
                                                       CHAPTER 2   Querying and Updating Data in the Database




By default, when you call the BO.AddNew method to add a new row, any required relations are
automatically set to point to the related row with the object ID -2. (Rows with the object ID -2 are
dummy rows that are automatically inserted when you create a ClarifyCRM database.) If you want
to establish a relation to a specific row, you need to change the relation to point to the specific row.
(For information on using the business objects to set up relations between rows, see Chapter 4,
Working with Database Relations.)

For example, all rows in the site table have a primary_org2site relation that identifies the
account that the site belongs to. When you call the BO.AddNew method, the new site row is
associated with the dummy account (the account with the object ID -2). If you want to associate the
site with a particular account, use the business objects to establish the relation to that account.

Note: Although the BO.AddNew method establishes relations to dummy rows with the object ID -2, you
should not use the object ID -2 to establish relations to dummy rows. Instead, use the BO.UnrelateAll
method to remove any existing relation (see Removing a Relation by Object ID and Name on page 211 for
details). This method sets the relation to point to a dummy row, if the relation is required.


After adding the row, you can commit the new row to the database by calling one of the update
methods. See Committing Changes to the Database on page 83 for details.


Generating the Object ID and ID Number for a New Row
As mentioned in Adding a New Row to the Rowset on page 70, when you add a new row to a business
object, a temporary object ID is assigned to the new row. In addition, if a field in the new row uses
a numbering scheme to generate new ID numbers (such as the id_number field of the case table,
which uses the Case ID numbering scheme), no ID number is generated in the new row.

In certain situations, you might need a real object ID and a real ID number to be assigned when the
new row is added to the business object. For example, you might be writing a component that can
enlist in a global transaction, and you might need to make the object ID and ID number available
before the transaction is committed. (For information on global transactions, see Using Global
Transactions on page 314.)

To change a business object so that the BO.addNew method automatically assigns a new object ID
and ID number, set the BO.IdGenMode property to CboConstants.cboOnAddNew:
   // Change the business object so that a new object ID and ID number are
   // assigned when the BO.addNew method is called
   boCase.setIdGenMode(CboConstants.cboOnAddNew);

By default, the BO.IdGenMode property is set to CboConstants.cboOnUpdate, which means
that the object ID and ID number are assigned when an update method of the business object or the
BoContext object is called.

Important: Once you add a new row to a business object, do not change the BO.IdGenMode property for
that business object.


For fields using numbering schemes, a new ID number is generated only if the NumSchemeName
property for that field is set. If you set the NumSchemeName property after calling the BO.addNew
method, a new ID number is automatically generated at that time.




                                                                                                          71
Adding New Rows in Database Tables




                 As explained in Setting the Numbering Scheme on page 75, most specific types of business objects are
                 designed to automatically set the NumSchemeName property for a field when you create the
                 business object. You do need to set this property manually.

                 The exception to this rule are business objects for tables with multiple numbering schemes for a
                 field. For example, in the site table, the site_id field can use one of the following numbering
                 schemes, based on the value of the type field:
                    Office ID (if type is 2, representing an internal office)
                    Individual ID (if type is 4, representing the site for an individual)
                    Site ID (for all other types)

                 Because the numbering scheme is based on the type of the site, the Site business object does not set
                 the NumSchemeName property of the site_id field until the changes in the business object are
                 committed to the database.

                 In these types of situations, if you need the generated ID number before the changes are committed
                 (for example, if you are writing an XBean that can enlist in global transactions), you can set the
                 NumSchemeName property manually:
                    If you set this property before calling the BO.addNew method, calling the BO.addNew method
                    generates the new ID number in the field.
                    If you set this property after calling the BO.addNew method, setting the property generates the
                    new ID number in the field.


                 Example of Adding a New Row
                 The following example illustrates how to add a new row to the database:
                    In Java (using the Field object):
                    import com.clarify.cbo.*;
                    ...
                    // Create a Generic business object for adding a queue.
                    Generic boQueue = boContext.createGenericBO("queue");
                    boQueue.setDataFields("*");
                    // Add a new row for the queue.
                    boQueue.addNew();
                    // Set the field values.
                    boQueue.getFields().getItem("title").setValue(strQueueTitle);
                    boQueue.getFields().getItem("allow_case").setValue(1);
                    boQueue.getFields().getItem("allow_subcase").setValue(0);
                    ... // Set relations and other field values. ...
                    // Save your changes.
                    boQueue.update();
                    ...




72
                                                          CHAPTER 2   Querying and Updating Data in the Database




   In JavaScript:
   var BoQueue;
   ...
   // Set up the Generic business object for adding queues.
   BoQueue = ClfyForm.CreateGenericBO("queue");
   // Add the first queue.
   BoQueue.AddNew();
   // Set the field values.
   BoQueue("title").Value = "Project";
   BoQueue("allow_case").Value = 1;
   BoQueue("allow_subcase").Value = 0;
   ... // Set relations and other field values. ...
   // Commit your changes.
   BoQueue.Update();


Adding a Row by Duplicating an Existing Row
The BO.AddNew method adds a new, empty row to the rowset. You need to fill in the required
fields before committing the new row to the database.

If you want to start with a copy of a row that has existing values for fields (rather than fill in each
field manually), you can use the BO.Duplicate method. For example, the following statement
copies the currently selected row in BoQueue1 to BoQueue2:
   In Java:
   BoQueue2.duplicate(BoQueue1);
   In JavaScript:
   BoQueue2.Duplicate(BoQueue1);

You can also copy an existing row to the rowset in the same business object:
BoQueue.Duplicate(BoQueue);

The BO.Duplicate method copies all fields except the object ID field. The copied fields include
any foreign key fields used for MTO relations. If the existing row has MTO relations to other rows,
the newly copied rows also have the same relations to these other rows.

Note: This method only copies field values, not child business objects or contained business objects. (For
details on child business objects and contained business objects, see Chapter 4, Working with Database
Relations.)


As is the case with the BO.AddNew method, the BO.Duplicate method generates a temporary
object ID for the new row. This temporary object ID uses the format ****NEW****<n>. When you
commit the new row to the database (see Committing Changes to the Database on page 83), the
business object assigns a real object ID to the new row and gets the new object ID of the row.




                                                                                                             73
Adding New Rows in Database Tables




                 For example, suppose you want to create three queues named Project, Project Dev, and Project QA.
                 You want all the queues to allow cases only (no other workflow items). You can set up a business
                 object (BoQueue) to add the first queue, then you can call the BO.Duplicate method to make
                 new copies of this queue. All copied queues have the same fields set. The following section of code
                 demonstrates how to do this:
                    In Java:
                    import com.clarify.cbo.*;
                    ...
                    // Create a Generic business object for adding a queue.
                    Generic boQueue = boContext.createGenericBO("queue");
                    boQueue.setDBObjectName("queue");
                    boQueue.setDataFields("*");
                    // Add a new row for the queue.
                    boQueue.addNew();
                    boQueue.getFields().getItem("title").setValue(strQueueTitle);
                    boQueue.getFields().getItem("allow_case").setValue(1);
                    boQueue.getFields().getItem("allow_subcase").setValue(0);
                    ... // Set relations and other field values. ...
                    // Duplicate the queue.
                    boQueue.duplicate(boQueue);
                    boQueue.getFields().getItem("title").append(" - Development");

                    // Move back to the first new queue.
                    boQueue.moveFirst();
                    // Duplicate the queue again.
                    boQueue.duplicate(boQueue);
                    boQueue.getFields().getItem("title").append(" - QA");
                    // Commit your changes. Since you are committing multiple rows
                    // in memory, use the BO.UpdateAll method instead of BO.Update.
                    boQueue.updateAll();
                    ...
                    In JavaScript:
                    var BoQueue;
                    ...
                    // Set up the Generic business object for adding queues.
                    BoQueue = ClfyForm.CreateGenericBO("queue");
                    // Add the first queue.
                    BoQueue.AddNew();
                    // Set the field values.
                    BoQueue("title").Value = "Project";
                    BoQueue("allow_case").Value = 1;
                    BoQueue("allow_subcase").Value = 0;
                    ... // Set relations and other field values. ...
                    // Duplicate the currently selected queue.
                    BoQueue.Duplicate(BoQueue);
                    BoQueue("title").Value += " - Dev";
                    // Move back to the first new queue.
                    BoQueue.MoveFirst();
                    // Duplicate the queue again.
                    BoQueue.Duplicate(BoQueue);
                    BoQueue("title").Value += " - QA";
                    // Commit your changes. Since you are committing multiple rows
                    // in memory, use the BO.UpdateAll method instead of BO.Update.
                    BoQueue.UpdateAll();



74
                                                                CHAPTER 2   Querying and Updating Data in the Database




        Setting the Numbering Scheme
        Some database tables use numbering schemes that you can configure in Policies and Customers or
        the Choicelist administration tool in ClarifyCRM Administrator for ClarifyCRM 13.0. (To view
        these numbering schemes, choose Numbering Scheme from the Setup menu. Data on these
        numbering schemes is stored in the num_scheme table.) For example, cases and sites have unique
        IDs that are generated when you add new rows.

        Before saving a new row in a Generic business object that uses a numbering scheme, make sure to
        set the Field.NumSchemeName property of the ID field to the name of the numbering scheme.
        This method sets the numbering scheme for the currently selected row.

        For example:
           In Java:
           boOpportunity.addNew();
           boOpportunity.getFields().getItem("id").setNumSchemeName(
              "Opportunity ID");
           In JavaScript:
           BoOpportunity.AddNew();
           BoOpportunity.Fields("id").NumSchemeName = "Opportunity ID";

        If you are using a business object other than Generic (such as Case, OEQuote, or Dialogue),
        skip this step. These other business objects automatically set the NumSchemeName property.

        Note: If an error occurs when committing the new row to the database, the value of the numbering scheme is
        still incremented. As a result, you may see gaps in your numbering scheme. For example, if you fail to
        commit a new case with the ID number 8, the next new case is assigned the ID number 9. No case in the
        database has the ID number 8.


        Although you can assign the ID manually, keep in mind that some database tables have indexes
        that enforce unique IDs. If you assign an ID that is already used by an existing row, an index
        violation will occur. Also note that if you set up the business objects to automatically generate an
        ID, the business objects will also update the num_scheme.next_value field after generating the
        ID. If you manually assign an ID, this field is not updated automatically.




Specifying Query Hints
        The ClarifyCRM applications support adding query hints to base business objects and extended
        filters.

        Note: No support is available for design-time query hint specification on CRM Views. There are no query
        hints in out-of-box data.


        The QueryHint property is used during a query to optimize the execution plan. This property
        may be read or set at any time.

        Two methods, BaseBO.setQueryHint() and BaseBO.getQueryHint(), add a hint string to
        the SELECT statement and return a query hint string, respectively.


                                                                                                                   75
Specifying Query Hints




                  BaseBO.setQueryHint
                  The BaseBO.setQueryHint() method adds an optimization hint string to the SELECT
                  statement. You are responsible for ensuring that the input string is a valid hint (or combination of
                  hints) with all the necessary parameters. All errors generated by a database server are reported as
                  exceptions.

                  Note: For Oracle database servers, a query hint is placed in the comments section. Oracle does not report any
                  errors resulted by processing query hints, but rather ignores the whole hint.


                  Here are a few examples of using setQueryHint.
                  com.clarify.cbo.Case getCaseBO()
                  CaseBo.getAddress().setQueryHint(" INDEX(table_address, ind_address_city) ");
                  CaseBo.query();
                  Generic viewBO = formContext.createBo("com.clarify.cbo.Generic");
                  viewBo.setDBObjectName("site_view");
                  viewBo.setQueryHint("USE_NL(table_address,table_site)");
                  viewBo.query();

                  See the Core Business Objects Reference Guide for more information about setQueryHint.


                  BaseBO.getQueryHint
                  The BaseBO.getQueryHint() method returns a query hint string that was already specified by
                  a user.

                  Every BaseBO.getQueryHint() call replaces the previously specified query string with the new
                  one. Before executing the query the specified hint string is parsed. If it contains table name
                  references (as hint parameters) and if the SQL statement uses single aliases for these table names,
                  then the table names in the hint string are replaced with the corresponding aliases. However, if any
                  of table names from the hint string have more than one alias in the SQL query statement, the entire
                  hint string is not included into the query and an error message is posted to the clfy log file and to
                  the console.

                  See the Core Business Objects Reference Guide for more information about getQueryHint.


                  Notes for Using setQueryHint and getQueryHint
                  This section describes general notes for using the setQueryHint and getQueryHint methods.

                  Query Hint Validity
                  You are responsible for the validity of the query hint string. A query hint string should consist of
                  one or more hints from the list of hints supported by the current version of database server CBOs
                  are accessing. Please note the following:
                         CBOs do not check validity of each hint or their combination.
                         Some hint parameters are index names; CBOs do not check validity of these names.




76
                                                       CHAPTER 2   Querying and Updating Data in the Database




Table References
Some hint parameters are table references, in which case you should specify table names. CBOs
replace those names with corresponding aliases as needed for each generated SQL SELECT clause.

If a table name, referenced in a query hint string, appears in a SELECT clause more than once, the
entire query hint string is discarded for this SELECT clause and an error is logged to the clfy log file
and to the console.

While parsing the query hint string, CBOs look for recognizable table names and replaces those
with known aliases.
   If you referenced a table name which does not appear in the SQL statement, this name is put
   into the query hint string as is.
   This means you can specify a query hint string which already has table aliases and this string is
   passed to the database server as is.

Executing a Query with QueryMode set to cbSubmitRequery
When a query is executed with QueryMode set to cboSubmitRequery, the specified query hint
string is not applied. This is because the re-query is done using cached object IDs (using IN-clause)
obtained from the original query; which used the hint, filter and other properties.

OR-operators in the FilterRelated Property
A query hint can be specified on a Base object that has OR-operator specified in the
FilterRelated property, which results in a UNION generated in the SQL statement. In this case
the query hint string specified on the Base object is included in each SELECT clause of the UNION.

One table name may appear in several SELECT clauses under different aliases. If this table name is
also present in the query hint string, it will be substituted by an alias used in the current SELECT
clause.

Some table names may not be present in every SELECT clause. If those table names are referenced
in the query hint string, they will be substituted by aliases only in those SELECT clauses where
they have aliases.

Here is an example using an Oracle database:
SELECT /*+ INDEX ( T3 , frcst_itm_objindex )              */ T1.objid, T1.name FROM
table_opportunity T1
INNER JOIN table_territory T2 ON
   T1.opp2territory = T2.objid
INNER JOIN table_frcst_itm T3 ON
   T1.objid = T3.item2opportunity
WHERE
   T1.objective = :P16 AND ( T2.terr_id =                :P17 ) AND ( T3.quantity <               :P18 )
UNION ALL SELECT /*+ INDEX ( table_frcst_itm             , frcst_itm_objindex ) */
T1.objid, T1.name FROM table_opportunity T1
INNER JOIN table_territory T2 ON
   T1.opp2territory = T2.objid
WHERE
   T1.objective = :P19 AND ( T2.terr_id =                :P20 ) ORDER BY 50




                                                                                                          77
Setting Field Values




Setting Field Values
                   To use an XVO to set the values of fields, see Setting Field Values in XVOs on page 132 in Chapter 3,
                   Working with XVOs.

                   If you are using the Field object to set the value of a field, set the Field.Value property:
                       In Java, use the following statement:
                       BO.getFields().getItem("Name").setValue(NewValue);
                       You can also use the following method as shorthand for the preceding statement:
                       BO.setValue("Name", NewValue);
                       NewValue should have the class or type that corresponds to the data type of the field. (You can
                       determine the data type by calling the Field.getType method.) For example, if the field
                       holds an integer, double, or boolean value, NewValue should be an int, double, or boolean.
                       In JavaScript, Visual Basic, or VBScript, use the following statement:
                       BO("Name").Value = NewValue
                       If you set the Field.Value property to a value that has a data type different than the data type
                       of the field, the business objects convert the value to the appropriate data type automatically.
                       (You can determine the data type of the field by getting the Field.Type property.) See Working
                       with Field Values and Data Types on page 92 for more information.

                   Note the following guidelines:
                       In a UI, you can specify some values by choosing an item from a pop-up list. In the business
                       objects, you can use ChoiceList and Choice objects to present the user with the same list of
                       selections. For details on using these objects, see Setting the Values of Choice Fields on page 120.
                       If you want to change the password for the current user, you cannot directly set the password
                       field in the user table or the web_user table. Instead, you need to use the
                       Session.ChangePassword method. See the Core Business Objects Reference Guide for details.
                       Some fields contain foreign keys (for example, the primary2bus_org field in the site table
                       implements the primary2bus_org relation). Although you can get the values of these types of
                       fields in a business object, you cannot directly set the values of these fields. If you want to
                       establish a relation between two rows, use the BO.RelateById method or any of the other
                       techniques described in Chapter 4, Working with Database Relations.
                       If you want to change the value in a row that is not currently selected, you can set the
                       Field.ValueAt property and specify the index of the row that you want to change.
                       If you need to append data to a string field that other applications may be updating at the same
                       time (for example, the case history field), use the Field.Append or Field.AppendAt method
                       instead of setting the Field.Value property. These methods append your data to the field in
                       the database at the time when you commit your change. This allows other applications to
                       update the field while your changes are in memory. When you commit your changes, your data
                       is appended to the current value of the field.




78
                                                                         CHAPTER 2   Querying and Updating Data in the Database




                     If you need to increment the numeric value of a field that other applications may be updating at
                     the same time, use the Field.Incr or Field.IncrAt method.
                     These methods increment the field in the database at the time when you commit your change.
                     This allows other applications to increment the field while your changes are still in memory.
                     When you commit your changes, the current value of the field is incremented.
                     For string fields, you can manipulate longer width data in memory using the
                     DisableWidthCheck property to remove any restriction on the width of data. Note that the
                     width check is disabled only for the data is in memory.




Deleting Rows from the Database
                 The user cannot delete most types of rows from the database with the ClarifyCRM applications.
                 For example, you cannot use a ClarifyCRM application to delete cases, contacts, sites, addresses,
                 users, employees, quotes, action items, or opportunities from the database.

                 Although you can use the business objects to delete database rows, you should use extreme caution
                 when deleting a row. For example, if you delete a contact from the database, any other rows that
                 are related to that contact (such as cases, opportunities, phone logs, and email logs) are no longer
                 related to any contact. Unresolved relations will cause problems with the ClarifyCRM applications.

                 To delete a row, select the row (using the Position property or one of the Move methods), and call
                 the BO.Delete method. To delete all rows in the rowset, call the BO.DeleteAll method.

                 Note: None of the rows are actually deleted from the database until you call the BO.UpdateAll method (or
                 the BoContext.updateAll method, if you are committing changes in all business objects created from
                 the BoContext object). See Committing Changes to the Database on page 83 for details.


                 When you delete a row, the business objects remove any relations to that row. If the key that defines
                 the relation is in another table, the business objects set the key to null. Since that key may be used in
                 a unique index, you should update that key before deleting the row.

                 For example, suppose that x and y tables are related through the y2x relation (see Figure 11). This
                 relation is implemented by the y2x field in the y table. If you delete a row from the x table, the
                 business objects set the y.y2x field to null for related rows in the y table.

Figure 11   How the business objects remove relations for deleted rows

                                           x table                       y table
                                                                                                       ... the business
                                   objid             ...   objid       role_name y2x                   object sets these
                                                           268435457   Default     268435457           keys to null.
                                   268435457         ...
 If you delete these rows...       268435458         ...   268435458   Default     268435458
                                   268435459         ...   268435459   Default     268435459


                 If the table containing the null keys has any unique indexes based on those keys, you may get index
                 violations. In the example of the x and y tables, the y table may have unique indexes based on the
                 y.y2x field. If the y.y2x field is set to null, you may get index violations, as shown in Figure 12.



                                                                                                                            79
Setting Constraints for Updates




                   Figure 12      How removed relations can violate unique indexes

                       If this table has a unique index based on the                   y table
                    combination of the role_name field and the y2x
                   relation, these rows now violate the uniqueness       objid       role_name y2x
                                               enforced by the index.
                                                                         268435457   Default     268435457
                                                                         268435458   Default     (null)
                                                                         268435459   Default     (null)


                   To avoid this problem, delete any related rows that contain foreign keys first. (You can also change
                   the relations to point to rows that you are not deleting.) In the example of the x and y tables, delete
                   the related rows from the y table before deleting any row from the x table.

                   Note: If you pass the cboUnrelateUserOnly flag as an argument to the BO.Delete and
                   BO.DeleteAll methods, these methods remove only the relations to custom database tables.




Setting Constraints for Updates
                   Before you commit changes to the database, you may want to constrain the update to occur only if
                   certain conditions are met. For example, suppose that you want to change the state of a routing
                   request from Pending to Cancelled. Before you commit your changes, the state of the request might
                   change to some other value. You want to change the state only if the request state is still Pending.

                   To do this, you can use the BO.AddCardinalityConstraint method to add a constraint to the
                   update transaction for the currently selected row:
                   BO.AddCardinalityConstraint(nConstraintType, strFilter, [strFilterRelated],
                   [strTableName]);

                   When you attempt to commit your changes, the business object evaluates the constraint. If the
                   constraint is not met, the update fails and the changes are not committed. To define the constraint:
                       Use the strFilter argument to specify a search filter to run against the current table (the table
                       represented by the business object). You can use the same syntax for this argument that you use
                       for the BO.Filter property. (See Specifying a Search Filter for the Current Table on page 44.)
                       If you want to specify the filter in terms of a table that is related to the current table, use the
                       strFilterRelated argument. You can use the same syntax for this argument that you use for
                       the BO.FilterRelated property. (See Specifying a Search Filter for Related Tables on page 47.)
                       (To specify a combination of filter criteria based on fields in the current table and in related
                       tables, use the strFilter and strFilterRelated arguments together.)
                       If you want to specify the search filter in terms of an unrelated table, use the strTableName
                       argument to specify the name of that table, and then specify the filter by using either the
                       strFilter argument, the strFilterRelated argument, or both arguments.




80
                                                      CHAPTER 2   Querying and Updating Data in the Database




   Use the nConstraintType argument to specify the number of rows that the query should
   return if you want the update transaction to be committed. Use one of the following constants
   for this argument (in Java, these constants are defined in the CBOConstants class):
   – cboQueryQualCheckZeroRows specifies that the transaction should be committed only if
     the query returns 0 rows.
   – cboQueryQualCheckOneRow specifies that the transaction should be committed only if the
     query returns 1 row.
   – cboQueryQualCheckZeroOrOneRow specifies that the transaction should be committed
     only if the query returns 0 or 1 row.
   – cboQueryQualCheckOneOrMoreRows specifies that the transaction should be committed
     only if the query returns 1 or more rows.

If the query does not return the specified number of rows, the update transaction fails.

For example, suppose that you want to require a routing request to be in the Pending state before
you change the state to Cancelled. To do this, add a constraint to the business object for the r_rqst
table (the table holding routing requests). When you define the constraint, set up the query filter to
find the request in the Pending state:
(objid = 'objid_of_request') AND (request_state = 0)

Then, specify that the update transaction should succeed only if that one matching row is found:
   If the routing request is in the Pending state, the query finds and returns that row, and the
   update transaction succeeds.
   On the other hand, if the routing request is not in the Pending state, the latter part of the query
   criteria fails to find the request. The query returns no rows, and the update transaction fails.

The following statement in Java demonstrates how to specify this constraint:
// Specify the search criteria for the constraint.
strConstraint = "(objid = " + clfySession.toSQLString(
   strObjid, CboConstants.cboSQLQuote, CboConstants.cboTypeString)
   + ") AND (request_state = " + nStatePending + ")";
// Specify the constraint.
boGeneric.addCardinalityConstraint(
   CboConstants.cboQueryQualCheckOneRow, strConstraint);

The following section of Java updates the state of a routing request to Cancelled only if the current
state of the request is Pending:
   In Java:
   int nStatePending = 0;
   int nStateRouted = 1;
   int nStateCancelled = 2;
   int nConstraintError = 147849276;
   String strObjid, strConstraint;
   ...
   try {
      ...
      // boGeneric is a Generic business object for the r_rqst table.
      // Set the value of the request_state field to Cancelled.
      boGeneric("request_state").Value = nStateCancelled;
      // Set up the constraint "(objid = '<object_id_of_request>') AND
      // (request_state = <pending>)".


                                                                                                         81
Setting Constraints for Updates




                          boGeneric.getFields().getItem(
                             "request_state").setValue(nStateCancelled);
                          // Apply the constraint to the current row. Define the constraint so
                          // the transaction succeeds only if one row matches the query criteria.
                          strConstraint = "(objid = " + clfySession.toSQLString(
                             strObjid, CboConstants.cboSQLQuote, CboConstants.cboTypeString)
                             + ") AND (request_state = " + nStatePending + ")";
                          boGeneric.addCardinalityConstraint(
                             CboConstants.cboQueryQualCheckOneRow, strConstraint);
                          // Attempt to commit the changes.
                          boGeneric.update();
                          // Clear the cardinality constraints.
                          boGeneric.clearAllCardinalityConstraints();
                       } catch(CboError e) {
                          // Check the error number in the Clarify Error object.
                          if (e.getNumber() == nConstraintError) {
                             // The constraint was not met, and the update failed.
                             ...
                          }
                       }
                       In JavaScript:
                       var nStatePending = 0;
                       var nStateRouted = 1;
                       var nStateCancelled = 2;
                       var cboQueryQualCheckOneRow = 0x00C0;
                       var cboSQLQuote = 0;
                       var cboTypeString = 5;
                       var nConstraintError = 147849276;
                       ...
                       try {
                          ...
                          // boGeneric is a Generic business object for the r_rqst table.
                          // Set the value of the request_state field to Cancelled.
                          boGeneric("request_state").Value = nStateCancelled;
                          // Set up the constraint "(objid = '<object_id_of_request>') AND
                          // (request_state = <pending>)".
                          strConstraint = "(objid = " + clfySession.ToSQLString(strObjid,
                             cboSQLQuote, cboTypeString) + ") AND (request_state = " +
                             nStatePending + ")";
                          // Apply the constraint to the current row. Define the constraint so
                          // the transaction succeeds only if one row matches the query criteria.
                          boGeneric.AddCardinalityConstraint(cboQueryQualCheckOneRow,
                             strConstraint);
                          // Attempt to commit the changes.
                          boGeneric.update();
                          // Clear the cardinality constraints.
                          boGeneric.ClearAllCardinalityConstraints();
                          ...
                       } catch(e) {
                          // Mask out the top 16 bits and determine if
                          // the error code indicates a Clarify error.
                          if ((e.number & 0xFFFF) == 1001) {
                             // Check the error number in the Clarify Error object.
                             if (boGeneric.Error.Number == nConstraintError) {
                                // The constraint was not met, and the update failed.
                                ...


82
                                                                 CHAPTER 2   Querying and Updating Data in the Database




                  }
                  ...
               }
               ...
           }




Committing Changes to the Database
        After you have made changes to the rows in memory, you can commit your changes to the
        database by calling one of the following methods:
           To commit only the changes to the currently selected row, call the BO.Update method.
           To commit all changes to all rows in the rowset, call the BO.UpdateAll method.
           To commit all changes from all business objects created from a BoContext object, call the
           BoContext.updateAll method. This commits the changes in a single transaction.
           If you have changes in several different components (for example, in separate save beans or
           XBeans), you can commit these changes in a single, global transaction. For details, see
           Chapter 9, Using Global Transactions and Bulk Queries.

        Important: If you are writing a Java component that can participate in a global transaction, do not call
        BO.update or BO.updateAll. Instead, call the BoContext.updateAll method. For details on
        global transactions, see Using Global Transactions on page 314.


        When you update a row in a parent business object, all exposed rows in child business objects (or in
        contained business objects that are children) are updated automatically. For details on parent-child
        relations and database updates, see Controlling Queries and Updates in Child Objects on page 196.

        If a database update fails, see Adding Errors to the Error Object in ActiveX on page 330 for guidelines
        on recovering from errors.

        If the changes include the addition of new rows to database tables, the business objects generate
        new object IDs for the new rows. (Until you commit the new rows to the database, the new rows
        have temporary object IDs in the form ****NEW****<n>.)

        Certain methods in specific business objects automatically commit changes to the database. For
        example, the CaseBO.Dispatch method automatically commits the changes for dispatching a
        case to a queue. See the Core Business Objects Reference Guide for more information on methods that
        automatically commit changes to the database.

        If you removed or deleted a row from the rowset, you must use the BO.UpdateAll method to
        commit the removal or deletion (or use the BoContext.updateAll method, if you are
        committing changes in all business objects created from the BoContext object). You cannot use the
        BO.Update method, which commits changes only to the currently selected row. Once you delete
        the row from the rowset in memory, the currently selected row is no longer the deleted row (see
        Figure 13).




                                                                                                                    83
Refreshing Cached Data in the Session




Figure 13    How BO.Update is affected by BO.Delete



                                  objid             ...                                    objid              ...
                                  268435457         ...                                    268435457          ...
             Currently                                                Currently
         selected row             268435458         ...            selected row            268435459          ...
                                  268435459         ...

                                 Rowset (before deletion)                                   Rowset (after deletion)

                          BoGeneric (a Generic business object)                     BoGeneric (a Generic business object)

                               When you remove or delete a row, the Position property identifies the next
                               row as the currently selected row.

                               When you call BO.Update, the business object commits changes to the
                               row 268435459. The deletion of row 268435458 is not committed to the
                               database until you call BO.UpdateAll.




Refreshing Cached Data in the Session
                  The ClarifyCRM Session object contains a copy of some of the data that provides the context of
                  the current user. For example, if a web user is currently logged in, the Session.Item property
                  provides access to information on the contact associated with the web user (such as the first name,
                  last name, email address, and phone number of the contact).

                  After you use the business objects to update data related to the current user, the corresponding
                  data in the Session object is not updated automatically. You must call the RefreshContext
                  method of the Session object to update the Session object with the changed data.

                  If you have changed the password of the current user, you need to specify the new password as an
                  argument when calling the Session.RefreshContext method.

                  For an example of refreshing session data, see Example of Modifying Contact Information on page 85.




84
                                                            CHAPTER 2   Querying and Updating Data in the Database




Example of Modifying Contact Information
        The following section of code searches the database for the contact associated with the current web
        user. The example changes the values of some of the fields in this row and commits the changes to
        the database.
           In Java:
           import com.clarify.cbo.*;
           ...
           String strNewPhone;
           String strNewEmail;
           // Find the contact associated with the current web user.
           Contact boContact = (Contact)
              boContext.createBO("com.clarify.cbo.Contact");
           boContact.setDataFields("*");
           boContact.setFilter("objid = " +
              clfySession.getItem("contact.id"));
           boContact.setSortOrder("last_name, first_name, phone");...
           boContact.query();
           // Change the phone number and email address of the contact.
           boContact.getFields().getItem("phone").setValue(strNewPhone);
           boContact.getFields().getItem("e_mail").setValue(strNewEmail);
           // Commit your changes to the database.
           boContact.update();
           // Refresh the user context information in the Session object
           // with your changes in the database. Since the password hasn’t
           // changed, you can pass an empty string, which allows the Session
           // object to use the existing password.
           clfySession.refreshContext();
           ...
           In JavaScript:
           var strNewPhone, strNewEmail;
           ...
           // Find the contact associated with the current web user.
           BoContact.DataFields = "*";
           BoContact.Filter = "objid = " + ClfySession.Item("Contact.Id");
           BoContact.Query()
           ...
           // Change the phone number and email address of the contact.
           BoContact("phone").Value = strNewPhone;
           BoContact("e_mail").Value = strNewEmail;
           // Commit your changes to the database.
           BoContact.Update();
           // Refresh the user context information in the Session object
           // with your changes in the database. Since the password hasn’t
           // changed, you can pass an empty string, which allows the Session
           // object to use the existing password.
           ClfySession.RefreshContext("");
           ...




                                                                                                               85
Querying and Updating Data Directly with SQL




Querying and Updating Data Directly with SQL
                  Although the business objects provide the ability to perform simple queries and updates of data in
                  the database, you may want to execute more complex SQL statements against the database.

                  For example, you may want to use aggregate SQL functions such as COUNT to get the number of
                  contacts associated with each site or SUM to get the total sales orders for each contact.

                  In order to perform these types of complex SQL statements, you can use the SqlExec object.
                  Unlike the business objects, the SqlExec object has properties that support the use of the
                  aggregate SQL functions COUNT, SUM, AVG, MIN, and MAX in a SELECT clause as well as the
                  ability to specify a GROUP BY clause.

                  In addition, you can directly specify a SQL statement that you want executed by the SqlExec
                  object. The SqlExec object also provides support for execution of stored procedures. You can use
                  stored procedures to execute a series of SQL statements or call other stored procedures.

                  Note: Although you can use this object to query the standard ClarifyCRM tables and views, you should not
                  use this object to update data in those tables. The SqlExec object does not contain any of the application
                  logic that the business objects contain. For example, the Case business object contains application logic to
                  create the appropriate activity log entries when you update case data; the SqlExec object does not contain
                  any of this logic. To avoid potential problems, use the Case business object to update case data, not the
                  SqlExec object.


                  The following sections explain how to execute more complex SQL statements:
                      Creating the SqlExec Object on page 86
                      Specifying a SQL Statement by Setting Properties on page 87
                      Explicitly Specifying a SQL Statement on page 88
                      Calling a Stored Procedure on page 89
                      Getting the Results from the SqlExec Object on page 90


                  Creating the SqlExec Object
                  To create the SqlExec object, use the Session.CreateObj method, and specify
                  "Clarify.SqlExec" as the argument:
                      In Java:
                      import com.clarify.cbo.*;
                      ...
                      SqlExec clfySqlExec =
                         (SqlExec) clfySession.createObj("Clarify.SqlExec");
                      In JavaScript:
                      clfySqlExec = clfySession.CreateObj("Clarify.SqlExec");




86
                                                                          CHAPTER 2    Querying and Updating Data in the Database




               Specifying a SQL Statement by Setting Properties
               If you want to use the SQL aggregate functions in a SELECT clause and you want to specify a
               GROUP BY clause, you can specify this search criteria as properties of the SqlExec object and use
               the object to construct a SQL statement. Table 4 lists the properties that you can set.
Table 4   SELECT statement clauses and corresponding SqlExec object properties
SELECT           Corresponding      Comments
Statement        SqlExec Object
Clause           Property
SELECT           DataFields         Set this property to a comma-delimited list of fields and SQL aggregate functions
                                    (SUM, MAX, MIN, AVG, and COUNT).
                                    You can also use the AS keyword to specify a different column heading name for a field
                                    or aggregate function (for example, "SUM(total_grand) AS TOTAL").
FROM             DBObjectName       Set this property to the name of the ClarifyCRM table or view that you want to query
                                    (omit the table_ prefix from the table or view name).
WHERE            Filter,            Set this property to the search criteria for finding specific rows in the table or view.
                 FilterRelated      You can use the same syntax here that you use for the BO.Filter and
                                    BO.FilterRelated properties. For details on this syntax, see Specifying a Search Filter
                                    for the Current Table on page 44 and Specifying a Search Filter for Related Tables on page 47.
ORDER BY         SortOrder          Set this property to a comma-delimited list of the fields that you want used to sort the
                                    results. These fields must also be specified in the SqlExec.DataFields property.
                                    You can use the same syntax here that you use for the BO.SortOrder property. For
                                    details on this syntax, see Specifying Sort Order on page 49.
GROUP BY         GroupBy            Set this property to a comma-delimited list of the fields that you want used to group the
                                    results. These fields must also be specified in the SqlExec.DataFields property.


               After you set these properties, call the SqlExec.Execute method to query the database. The
               SqlExec object uses the properties that you specify to construct a SQL statement and executes that
               statement against the database.

               For example, suppose that you want to generate a list of each site and the number of contacts at the
               site. In SQL, you can get this list by executing the following SQL statement:
               SELECT SITE, COUNT(*) AS CONTACTS
               FROM TABLE_ROL_CONTCT
               GROUP BY SITE

               (The AS CONTACTS section of the clause specifies that the number of contacts is listed in the
               results under the heading CONTACTS.)




                                                                                                                                87
Querying and Updating Data Directly with SQL




                  The following example shows how to use the SqlExec object to set up and execute this statement:
                      In Java:
                      import com.clarify.cbo.*;
                      ...
                      SqlExec clfySqlExec = null;
                      String strDBObjectName = "rol_contct";
                      String strDataFields = "site, count(*) as contacts";
                      String strGroupBy = "site";
                      // Create the SqlExec object.
                      clfySqlExec = (SqlExec) clfySession.createObj("Clarify.SqlExec");
                      // Specify the SQL statement by setting its properties.
                      clfySqlExec.setDBObjectName(strDBObjectName);
                      clfySqlExec.setDataFields(strDataFields);
                      clfySqlExec.setGroupBy(strGroupBy);
                      // Execute the SQL statement.
                      clfySqlExec.execute();
                      ...
                      In JavaScript:
                      ...
                      var strDBObjectName = "rol_contct";
                      var strDataFields = "site, count(*) as contacts";
                      var strGroupBy = "site";
                      ...
                      // Create the SqlExec object.
                      clfySqlExec = clfySession.CreateObj("Clarify.SqlExec");
                      // Specify the SQL statement by setting its properties.
                      clfySqlExec.DBObjectName = strDBObjectName;
                      clfySqlExec.DataFields = strDataFields;
                      clfySqlExec.GroupBy = strGroupBy;
                      // Execute the SQL statement.
                      clfySqlExec.Execute();
                      ...

                  For details on getting the results, see Getting the Results from the SqlExec Object on page 90.


                  Explicitly Specifying a SQL Statement
                  If you want to explicitly specify a SQL statement to execute (rather than have the SqlExec object
                  build that SQL statement from the properties that you set), pass the SQL statement as an argument
                  to the SqlExec.Execute method.

                  Note: Some database servers restrict SQL statements to a maximum length. If you specify a SQL statement
                  that is longer than the maximum, an error occurs.


                  Note: Although you can use the INSERT, DELETE, and UPDATE statements to modify data in the
                  database, you should not use these statements to modify data in the standard ClarifyCRM tables and views. If
                  you need to update data in these tables, use the corresponding business object for the table.




88
                                                       CHAPTER 2   Querying and Updating Data in the Database




The following example executes a SQL statement that lists the contacts in the database and the sum
of each contact’s sales orders.
   In Java:
   import com.clarify.cbo.*;
   ...
   SqlExec clfySqlExec = null;
   String strSqlStatement =
      "select c.first_name, c.last_name, sum(s.total_grand) as total
      from table_contact c, table_contract o, table_contr_schedule s
      where c.objid = o.primary2contact and o.struct_type = 2 and
      o.objid = s.schedule2contract
      group by c.first_name, c.last_name";
   ...
   // Create the SqlExec object.
   clfySqlExec =
      (SqlExec) clfySession.createObj("Clarify.SqlExec");
   // Execute the SQL statement.
   clfySqlExec.execute(strSqlStatement);
   ...
   In JavaScript:
   var strSqlStatement =
   "select c.first_name, c.last_name, sum(s.total_grand) as total
   from table_contact c, table_contract o, table_contr_schedule s
      where c.objid = o.primary2contact and o.struct_type = 2 and
      o.objid = s.schedule2contract
      group by c.first_name, c.last_name";
   ...
   // Create the SqlExec object.
   clfySqlExec = clfySession.CreateObj("Clarify.SqlExec");
   // Execute the SQL statement.
   clfySqlExec.Execute(strSqlStatement);
   ...

Note: In Oracle, do not end the SQL statement string with a semicolon (“;”).


For details on getting the results, see Getting the Results from the SqlExec Object on page 90.


Calling a Stored Procedure
If you want to call a stored procedure, pass an EXEC statement as an argument to the
SqlExec.Execute method.

This example assumes that you have one of the following stored procedures defined in your
database.

For Microsoft SQL Server:
CREATE PROCEDURE MY_GET_SITE_BY_SITE_ID @MY_SITE_ID VARCHAR(80)
AS
SELECT OBJID, SITE_ID, NAME
FROM TABLE_SITE
WHERE SITE_ID = @MY_SITE_ID




                                                                                                          89
Querying and Updating Data Directly with SQL




                  For Oracle:
                  CREATE PACKAGE MY_SITE_DATA IS
                     TYPE SITECURTYP IS REF CURSOR;
                     PROCEDURE MY_GET_SITE_BY_SITE_ID (MY_SITE_ID IN VARCHAR2,
                        MY_SITE_CV IN OUT SITECURTYP);
                  END;
                  CREATE PACKAGE BODY MY_SITE_DATA IS
                     PROCEDURE MY_GET_SITE_BY_SITE_ID (MY_SITE_ID IN VARCHAR2,
                        MY_SITE_CV IN OUT SITECURTYP) IS
                     BEGIN
                        OPEN MY_SITE_CV FOR
                        SELECT OBJID, SITE_ID, NAME
                           FROM TABLE_SITE
                           WHERE SITE_ID = MY_SITE_ID;
                     END;
                  END MY_SITE_DATA;

                  For Oracle, you can get a result set from a stored procedure by passing a reference cursor as an
                  input parameter (SITECURTYP in the example above). You can only get one result set at each
                  execution of a stored procedure. You must specify the reference cursor as the last parameter in the
                  stored procedure definition.

                  The following example executes these stored procedures.
                  import com.clarify.cbo.*;
                  ...
                  SqlExec cboSqlExec = null;

                  For Microsoft SQL Server, you set up the EXEC statement this way:
                  strSqlCall = "EXEC MY_GET_SITE_BY_SITE_ID '" + strSiteId + "'";

                  For Oracle, you set up the EXEC statement this way:
                  strSqlCall = "EXEC MY_SITE_DATA.MY_GET_SITE_BY_SITE_ID('" +
                             strSiteId + "')";

                  You then create the SqlExec object and call its execute method:
                  cboSqlExec = (SqlExec) cboSession.createObj("Clarify.SqlExec");
                  cboSqlExec.execute(strSqlCall);

                  For details on getting the results, see Getting the Results from the SqlExec Object on page 90.


                  Getting the Results from the SqlExec Object
                  After you call the SqlExec.Execute method to execute a SQL statement or stored procedure, the
                  object sets its RowCount property to the number of rows returned and the FieldCount property
                  to the number of fields returned.

                  To access a value in a specific row and field, call the SqlExec.GetValue method, and pass in the
                  row position and field position. The row and field positions are 1-based. (For example, to get the
                  value from the first row and third field, call SqlExec.GetValue(1,3).)

                  To get the column name from the results, call the SqlExec.GetValue method, and pass -1 as the
                  row position.




90
                                                   CHAPTER 2   Querying and Updating Data in the Database




The following example demonstrates how to iterate through the results of the query and print out
the results.
   In Java:
   import com.clarify.cbo.*;
   ...
   SqlExec clfySqlExec = null;
   int nRow, nField = 1;
   if (clfySqlExec.getRowCount() != 0) {
      // Get and print the column names.
      for (nField = 1;nField <= clfySqlExec.getFieldCount();nField ++){
         System.out.print(clfySqlExec.getValue(-1, nField) + "\t");
      }
      System.out.println("\n=======================================");
      // Get and print each row of data.
      for (nRow = 1; nRow <= clfySqlExec.getRowCount(); nRow++) {
         // Get and print the value of each field.
         for (nField = 1; nField <= clfySqlExec.getFieldCount();
            nField++) {
            System.out.print(clfySqlExec.getValue(nRow, nField) + "\t");
         }
         System.out.println();
      }
   } else {
      System.out.println("No results found.");
   }
   ...
   In JavaScript:
   ...
   // This example assumes that you are using cscript to execute the
   // code. In cscript (a command-line script interpreter), you
   // use the WScript.Echo method to print data to the console window.
   var strOutput = "";
   var nField, nRow = 1;
   if (clfySqlExec.RowCount != 0) {
      // Get and print the column names.
      for (nField = 1; nField <= clfySqlExec.FieldCount; nField++) {
         strOutput += clfySqlExec.GetValue(-1, nField) + "\t";
      }
      WScript.Echo(strOutput);
      WScript.Echo("===============================================");
      // Get and print each row of data.
      for (nRow = 1; nRow <= clfySqlExec.RowCount; nRow++) {
         strOutput = "";
         // Get and print the value of each field.
         for (nField = 1; nField <= clfySqlExec.FieldCount; nField++) {
            strOutput += clfySqlExec.GetValue(nRow, nField) + "\t";
         }
         WScript.Echo(strOutput);
      }
   } else {
      WScript.Echo("No results found.");
   }
   ...




                                                                                                      91
Working with Field Values and Data Types




Working with Field Values and Data Types
                  The following sections contain general guidelines for working with the values that you get and set
                  in field values in CBO and in XVOs:
                     About Field Values and Their Data Types on page 92
                     Working with String Representations of Values on page 93
                     Working with Decimal and Currency Values on page 99
                     Working with Date-Time Values on page 102


                  About Field Values and Their Data Types
                  When you get the value of a field, the data type of the value corresponds to the data type of the
                  database field:
                     If you are using ActiveX (for example, if you are writing in Visual Basic or VBScript), the
                     business objects return field values as variants.
                     Each variant has a subtype corresponding to the data type of the database field. For example, if
                     you use a business object to get the value of a datetime database field (such as the
                     case.creation_time field), the business object returns a variant of the date-time subtype. If
                     you use a business object to get the value of an integer field, the business object returns a variant
                     of the integer subtype.

                     Note: For fields that contain foreign keys (such as objid and case_reporter2site), business
                     objects always return the object IDs as variant strings.

                     If you are executing code in JavaScript, the variants returned by the business objects are
                     automatically converted to the appropriate JavaScript data type. For example, a variant of the
                     string subtype becomes a string, and a variant of any numeric subtype becomes a number. Note
                     the following exceptions:
                     – Date-time fields return date variants (VT_DATE values). If you need to convert a date variant
                       to a JavaScript Date object, pass the date variant to the JavaScript Date object constructor.
                     – If the Session.JScript property is set to True, decimal and currency fields return string
                       values instead of numeric values. (For more information about this property, see Working with
                       Business Objects in JavaScript on page 303.)
                     If you are using an XVO in Java to access the field value, the XVOs return field values as objects
                     of the appropriate types (for example, java.util.Date for date-time values,
                     java.math.BigDecimal for decimal values, and com.clarify.xvobase.ClfyCurrency
                     for currency values).
                     For example:
                     import java.util.*;
                     Date dateModified = myCaseVo.getModifyStmp();
                     For details on accessing field values in XVOs, see Getting and Setting Field Values in an XVO on
                     page 130.




92
                                                      CHAPTER 2   Querying and Updating Data in the Database




   If you are using the Field object in Java to access the field value, the business objects return
   field values as objects that you need to cast as the appropriate class.
   For example, if you use a business object to get the value of a datetime database field (such as
   the case.creation_time field), the business object returns an object. If you want to work
   with this object as a date-time value, you need to cast the object as a java.util.Calendar:
   import java.util.*;
   Calendar myVar =
      (Calendar) BO.getFields().getItem("Name").getValue();
   Note the following:
   – When you get the value of a varchar database field (such as the case.title field), you
     can use the BO.toString or Field.toString method instead of casting the object as a
     String.
   – Decimal and currency fields return string representations of values. You must cast the
     returned objects as String.


Working with String Representations of Values
In a web application, the user interface is constructed from text (HTML) generated by your script
code. If you want to display the value of a nonstring field (such as an integer or date-time value),
you must get the string representation of that value.

The following sections discuss how to convert between a value and its string representation:
   How Locale and Time Zone Affect Values on page 93
   Getting String Representations of Field Values on page 94
   Specifying Custom Formats for String Representations on page 96
   Converting a String Representation to a Value on page 98
   Setting Field Values to String Representations on page 98

How Locale and Time Zone Affect Values
When using a web application, end users expect to view and enter data in terms of their own locale
and time zone.

For example, when viewing decimal data, French or German users expect to see a comma (,) used
as a decimal separator, rather than a period (.). For digit grouping symbols, French users expect to
see spaces (for example, 123 456 789,00) and German users expect to see periods (for example,
123.456.789,00), instead of commas (for example, 123,456,789.00). When viewing date-time values,
users expect to see the values expressed in terms of their own time zones.

Similarly, when entering decimal data in to fields in a form, French or German users use commas as
decimal separators and spaces or periods as digit grouping symbols. When entering date-time
values in to fields in a form, end users enter the values in terms of their own time zones.




                                                                                                         93
Working with Field Values and Data Types




                  Your web application must account for these differences in locale and time zone. Because the value
                  of a field in a business object is a locale-independent value, you must express these values in terms
                  of the end user’s locale and time zone when displaying these values in an HTML page. Similarly,
                  when you process form data submitted by the end user, you must assume that the incoming data
                  uses locale-dependent formats. You must also assume that any submitted date-time values are
                  expressed in terms of the end user’s time zone.

                  The business objects provide methods with which you can get the string representations of
                  nonstring values and vice versa. These methods use the end user’s locale and time zone (based on
                  settings in the ClarifyCRM Session object) when performing the conversion. For example, if the
                  standard French locale is specified in the Session object, the business object methods assume that
                  periods should be used as digit grouping symbols when processing data entered by the user and
                  when generating string representations of numeric values.

                  To initialize the locale setting in the Session object, prompt the end user to select a locale and time
                  zone at the beginning of a session. Then, use the ClarifyCRM Session.SetLocale method to
                  store this information in the Session object for later use.

                  For more information on setting the locale and time zone of the end user, see Setting the Locale and
                  Time Zone on page 272.

                  Getting String Representations of Field Values
                  To get the string representation of a nonstring value in a business object field (such as a numeric,
                  decimal, currency, or date-time field), use one of the following techniques:
                     If you are using an XVO to get the field value, pass the value to one of the CBO Session object
                     toString methods (Session.toString or a method listed in Table 6 on page 97):
                     // Getting the string representation of a date-time value from an XVO
                     String strDate = cboSession.toDateString(siteVo.getUpdateStamp());
                     If you are using the Field object to get the field value, use the Field.ToString method or
                     the CBO Session object ToString methods. In Java, you can use the BO.toString method.

                  The CBO ToString methods format the string in terms of the end user’s locale. In addition, if the
                  field value is a date-time value, the CBO ToString methods return a string that expresses the
                  value in terms of the end user’s time zone (unless the display_mstr_time_zone configuration
                  item is present, as explained in Overriding Display in Master Time on page 106).

                  Note: Do not use the built-in conversion functions of a scripting language to convert values for display.
                  These functions format strings in terms of the locale/time zone of the local machine, not the user’s machine.


                  The CBO ToString methods use settings in the ClarifyCRM Session object to determine the end
                  user’s locale. To determine the appropriate formats for that locale, the business objects use a set of
                  predefined, locale-specific formats. Figure 14 illustrates the effect of using the Field.ToString
                  method in a JSP page to convert a nonstring value to its string representation.




94
                                                                            CHAPTER 2     Querying and Updating Data in the Database




Figure 14   Differences between built-in string conversion functions and Field.ToString


                                                      JSP Page
   Decimal (value): <%=BoQuote.getLineItem().getFields().getItem("d_adj_pct").getValue()%>
   Decimal (ToString): <%=BoQuote.getLineItem().getFields().getItem("d_adj_pct").toString()%>

   <%
   BoQuote.getLineItem().setCurrencyForDisplay(BoQuote.getCurrency().getFields().getItem("name").getValue());
   %>
   Currency (value): <%=BoQuote.getLineItem().getFields().getItem("handling_cost").getValue()%>
   Currency (ToString): <%=BoQuote.getLineItem().getFields.getItem("handling_cost").toString()%>

   Date (value): <%=BoQuote.getLineItem().getFields().getItem("start_date").getValue()%>
   Date (ToString): <%=BoQuote.getLineItem().getFields().getItem("start_date").toString()%>

                                                      Web server machine
                                                                                           Locale: English (United States)
                                                                                           Time zone: PST
     The string representations generated by the
     Field.ToString method use the correct formatting for                                        Locale: German (Standard)
     the end user’s locale (standard German).                                                    Time zone: GMT+1


                                                                       Resulting Web Page
                                                            Decimal (value): 0.85
                                                            Decimal (ToString): 0,85
                                                            Currency (value): 1234.56
                                                            Currency (ToString): 1.234,56 US$
                                                            Date (value): 4/12/00 7:43:04 AM
                                                            Date (ToString): 12.04.00 15:43:04

                                                                        End user’s machine


                As demonstrated in Figure 14, the CBO ToString methods use formatting appropriate for the end
                user’s locale. In this example, the end user’s locale is standard German. The Field.ToString
                method uses a set of predefined formats for the standard German locale.

                In comparison, the scripting environment uses the locale of the web server machine (not the end
                user’s machine) when converting the variants to strings. The strings (implicitly converted by
                writing out the Field.Value property) use formats appropriate to the web server’s locale (US
                English), not the end user’s locale (standard German).

                In addition, the CBO ToString methods express date-time values in terms of the end user’s time
                zone (GMT+1), rather than in terms of the web server’s time zone (PST). (In Figure 14, writing out
                the Field.Value property expresses the date-time value in terms of the web server’s time zone.)




                                                                                                                                 95
Working with Field Values and Data Types




                  The following section of code illustrates how to use the CBO ToString method to display
                  nonstring values to the end user:
                     In Java:
                     ...
                     // If you are using an XVO to get the value, pass the value to one of
                     // the CBO Session object toString methods.
                     SiteVo siteVo = (SiteVo) genSiteBo.getVo();
                     System.out.println(cboSession.toDateString(siteVo.getUpdateStamp()));
                     // If you are using shortcuts to the Field object methods to get the
                     // value, use the BO.toString method.
                     System.out.println(genSiteBo.toString("update_stamp"));
                     In JavaScript (ASP):
                     ...
                     // Use the Field.ToString method to display currency values, date-time
                     // values, decimal values, and integer values to the end user.
                     var colFields = BoQuote.OELineItem.Fields;
                     <P>Handling Cost: <%=colFields("handling_cost").ToString()%></P>
                     <P>Start Date: <%=colFields("start_date").ToString()%></P>
                     ...

                  Specifying Custom Formats for String Representations
                  The CBO ToString methods use a set of predefined, locale-specific formats. To change these
                  formats for the session, set the attributes in Table 5 in the ClarifyCRM Session.Item property.
                  Table 5    Changing string formats
                  Format                                               Corresponding Attribute in Session.Item Property
                  Number of digits between digit group separators (for Locale.GroupSize
                  example, in the string "1,234,567", there are three
                  digits between the group separators)
                  Number of digits after the decimal point             Locale.ScaleDigits
                  Format for displaying negative numbers               Locale.NegativeNumberOrder
                  Format for displaying positive currency numbers      Locale.PositiveCurrencyOrder
                  Format for displaying negative currency numbers      Locale.NegativeCurrencyOrder
                  Default format for converting date-time values       Locale.ShortDateFormat
                  Long format for converting date-time values          Locale.LongDateFormat


                  For details on the values for these attributes, see the Core Business Objects Reference Guide.

                  The following statement changes the short date format for the session:
                     In Java:
                     ClfySession.setItem("Locale.ShortDateFormat",
                        "MMMM dd, yyyy HH:mm tt");
                     In JavaScript:
                     ClfySession.Item("Locale.ShortDateFormat") =
                        "MMMM dd, yyyy HH:mm tt"




96
                                                               CHAPTER 2   Querying and Updating Data in the Database




After you call this method, the Field.ToString method and the Session.ToString method
use the specified format to get the string representation of a date-time value. These methods use
the specified format for the entire session.

If you want to override the format only for selected values (as opposed to the entire session), you
can use the following ClarifyCRM Session object methods (see Table 6).

Table 6     Methods for overriding the default string format
Data Type            Method for Converting to String           Formats That You Can Override
numeric              Session.ToNumberString                      You can specify the number of digits displayed
                                                                 after the decimal point.
                                                                 You can include or exclude the digit group
                                                                 separators.
                                                                 You can convert the value to a percentage
                                                                 (including the percent sign).
currency             Session.ToCurrencyString                    You can specify the currency code and the
                                                                 currency symbol displayed.
                                                                 You can specify the number of digits displayed
                                                                 after the decimal point.
                                                                 You can include or exclude the digit group
                                                                 separators.
date-time            Session.ToDateString                        You can specify the exact format of the date and
                                                                 time value.


For more information on these methods, see the Core Business Objects Reference Guide.

The following statement uses the Session.ToNumberString method to get the string
representation of a decimal value. The statement expresses the value as a percentage rounded to
the nearest whole. (The returned string includes the percent sign.) For example, if the value of this
field is 0.85, the statement returns the string "85%".
   In Java:
   String strValue =
      clfySession.toNumberString(
      boOEQuote.getLineItem().getValue("d_adj_pct"), 0, false, true);
   In JavaScript:
   var strValue =
      ClfySession.ToNumberString(
      BoQuote.OELineItem("d_adj_pct").Value, 0, false, true)




                                                                                                                  97
Working with Field Values and Data Types




                  Converting a String Representation to a Value
                  If you are developing a form in a web application, all values submitted by the end user are
                  returned as strings. If you need to convert any of these strings to integers, floats, doubles, or
                  date-time values, use the Session.FromString method:
                     In Java:
                     myValue = (Class) Session.fromString(strValue, CboConstants.enumType)
                     This method returns an object that you must cast as the appropriate class (for example,
                     Integer, Float, Double, Calendar).
                     In JavaScript or Visual Basic:
                     var myValue = Session.FromString(strValue, enumType)
                     This method returns a variant.

                  This method accounts for the locale and time zone of the end user (which are specified in the
                  Session object) when parsing string values. For example, in the standard German numeric
                  settings, the digit grouping symbol is a period (.) and the decimal separator is a comma (,). If the
                  locale of the current session is set to standard German and you pass the string "1.234,56" to the
                  Session.FromString method, the method recognizes the separators correctly and converts the
                  string to a numeric value.

                  For the enumType argument, specify the constant for the data type that you want to convert to. For
                  example, to convert a string to an integer, use cboTypeInteger.

                  The following example in Java converts a string representation of an integer (strValue) to an
                  object of the Integer class:
                  Integer nValue = (Integer) Session.fromString(strValue,
                     CboConstants.cboTypeInteger);

                  Note: In JavaScript (if you set Session.JScript to True) and in Java, currency and decimal values are
                  treated as strings. If you specify cboTypeCurrency or cboTypeDecimal as an argument to the
                  Session.FromString method, the method returns a string value.


                  Setting Field Values to String Representations
                  To set the value of a field, set the Field.Value property to the new value. If the data type of the
                  field does not match the data type of the assigned value, the business objects attempt to convert the
                  value to the data type of the field automatically.

                  Note: For string representations of date-time values, the business object also converts from the end user’s
                  time zone to the time zone of the local machine. See Working with Date-Time Values on page 102 for details.


                  If you set a field to the string representation of a value, the business objects expect the string to be
                  formatted in terms of the end user’s locale and time zone (the locale and time zone set in the
                  ClarifyCRM Session object). The business objects attempt to parse the string using the format for
                  this locale and time zone.

                  For example, if the end user’s locale is standard German, the business objects expect the value to
                  include commas as decimal separators and periods as digit separators. For example, the business
                  objects interpret the string value "123.456,78" as 123456.78 and "123,456.78" as 123.4568.



98
                                                      CHAPTER 2   Querying and Updating Data in the Database




When you are setting a field to the string representation of a value, use the following guidelines:
   If you are setting a numeric or currency field to the string representation of a numeric or
   currency value, use the following characters in the string:
   – numeric digits (0–9)
   – a single, locale-dependent, decimal separator (optional)
   – a single minus sign (optional), either at the beginning or end of the string
   All other characters in the string are removed and ignored.
   If you are setting a date-time field to the string representation of a date-time value, use one of
   the following formats for the string:
   – the ISO standard format for date and the military format for time, optionally including the
     time zone (for example, 1999-09-09 20:31:36)
   – the locale-dependent short format for date-time (using the current locale of the session)


Working with Decimal and Currency Values
The following sections cover guidelines for fields that represent decimal or currency values:
   Getting String Representations of Currency Values on page 99
   Calculating Decimal and Currency Values on page 100

Getting String Representations of Currency Values
Before you use the ClarifyCRM Session or Field object methods to get the string representation
of a currency value, you must specify the currency for the value. To generate a string
representation, the methods need the appropriate currency symbol and number of fractional digits.

By default, the business objects are designed to use the currency related to the current row for
display purposes. When you call the BO.ToString or the BO.toHtmlString method for a
currency field, the business object returns a string that uses the currency related to the current row.
You do not need to explicitly set the currency for display.

However, if the business object has no relation to the currency table, you must set the
BO.CurrencyForDisplay or Session.Item("Currency.CurrencyForDisplay")
property to specify the currency to use. To get the string representation of a currency value in this
case, you should do the following:
1. Get the name or the ISO code of the currency used by the value.
   For example, in the OEQuote business object, you can get the name and ISO code of the
   currency for the order from the OEQuoteBO.Currency("name").Value and
   OEQuoteBO.Currency("iso_code").Value properties (respectively).
2. Set the BO.CurrencyForDisplay property to the name or ISO code of the currency. The
   Field.ToString method uses this to generate the string representation of the value.
3. Use the Field.ToString method to convert the currency value to a string representation.

If you want to use the Session.ToString or Session.ToCurrencyString methods, set the
Session.Item("Currency.CurrencyForDisplay") property to the currency name or ISO
code. Then, you can use the Session.ToString or Session.ToCurrencyString methods to
get the string representation of the value.


                                                                                                         99
Working with Field Values and Data Types




                  When you call the Field.ToString method on a field with a currency value, the business object
                  uses the currency name or ISO code to get the currency symbol and the number of fractional digits.
                  The business object gets the currency name or ISO code in the following way:
                  1. If the BO.CurrencyForDisplay property is set for the business object, the object uses the
                     name or ISO code specified by that property.
                  2. If the BO.CurrencyForDisplay property is set for the parent of the business object, the object
                     uses the name or ISO code specified by that property.
                     If the BO.CurrencyForDisplay property is not set in the parent business object, the object
                     checks this property in the parent of that parent business object. The object continues checking
                     the parents upward through the parent-child hierarchy until the object finds a setting for the
                     BO.CurrencyForDisplay property. (For details on parent-child hierarchies, see
                     Understanding Parent-Child Relationships on page 180.)
                  3. If the BO.CurrencyForDisplay property is not set for any of the parent business objects, the
                     business object calls the ClarifyCRM Session.ToString method, which uses the name or
                     ISO code specified by the Session.Item("Currency.CurrencyForDisplay") property.

                  For example, the following section of code gets the string representation of a currency value:
                     In Java:
                     ...
                     // Set the currency to use for the business object.
                     BoOEQuote.setCurrencyForDisplay(
                        (String)BoOEQuote.getCurrency().getValue("name"));
                     // Get the string representation of a currency value.
                     strCurrency = BoOEQuote.toString("total_net");
                     ...
                     In JavaScript:
                     ...
                     // Set the currency to use for the business object.
                     BoOEQuote.CurrencyForDisplay = BoOEQuote.Currency("name").Value;
                     // Get the string representation of a currency value.
                     strCurrency = BoOEQuote.ToString("total_net");
                     ...

                  Calculating Decimal and Currency Values
                  In the ClarifyCRM database, currency and decimal values are stored with a precision of 19, 4 (19
                  total digits, which includes 4 fractional digits).

                  If you need to perform calculations, use the ClarifyCRM Calculator object instead of the built-in
                  scripting language functions. The built-in functions in JavaScript do not provide the same level of
                  precision as the values in the database. The built-in functions in Visual Basic and VBScript do not
                  provide the higher level of precision required for intermediate results. The ClarifyCRM
                  Calculator object stores intermediate results in a precision of 44, 10 (44 total digits, which
                  includes 10 fractional digits) and returns results in a precision of 19, 4.




100
                                                    CHAPTER 2   Querying and Updating Data in the Database




To create the Calculator object, use the Session.CreateObj method, and pass the
programmatic ID "Clarify.Calculator" as an argument. You can then use the
Calculator.Calculate method to perform calculations. You can pass in a combination of up to
seven values and arithmetic operators. You can specify the values as numeric values or as string
values (for example, "27.01"). If the specified values are not numeric, the Calculator object
assumes that the values are strings in a locale-dependent format. (The Calculator object uses the
current locale settings of the session to determine the format.)

To get the results, use the Calculator.GetCalcResult method. If you are using Java, or if you
have set the Session.JScript property to True (which treats currency and decimal values as
strings in a locale-dependent format), the result is returned as a string. (For more information on
the Session.JScript property, see Working with Business Objects in JavaScript on page 303.)

For example, the following section of code adds the values of the sched_net_amt,
sched_tax_amt, ship_amt, and handling_cost fields in the OEQuote.Schedule business
object. The result of the calculation is placed in the total_grand field.
   In Java:
   ...
   // Get the Calculator object.
   calculator =
      (Calculator)clfySession.createObj("Clarify.Calculator");
   boSchedule = BoOEQuote.getSchedule();
   // Sum up subtotal, shipping, handling, tax to get the grand total.
   calculator.calculate(
      (String) boSchedule.getValue("sched_net_amt"),
      (String) boSchedule.getValue("sched_tax_amt"), "+",
      (String) boSchedule.getValue("ship_amt"), "+",
      (String) boSchedule.getValue("handling_cost"), "+");
   boSchedule.getFields().setValue("total_grand",
      calculator.getCalcResult());
   ...
   In JavaScript:
   ...
   // Get the Calculator object.
   var calculator = ClfySession.CreateObj("Clarify.Calculator");
   var BoSchedule = BoOEQuote.Schedule;
   // Sum up subtotal, shipping, handling, tax to get the grand total
   calculator.Calculate(BoSchedule("sched_net_amt").Value,
      BoSchedule("sched_tax_amt").Value, "+",
      BoSchedule("ship_amt").Value , "+",
      BoSchedule("handling_cost").Value, "+");
   BoSchedule("total_grand").Value = calculator.GetCalcResult();
   ...




                                                                                                      101
Working with Field Values and Data Types




                    Working with Date-Time Values
                    When working with date-time values in web applications (or in other applications where the
                    machine processing your script code is different than the machine displaying the data to the end
                    user), you need to be aware of the differences between the following time zones:
                        End user’s profile. If you need to present a date-time value to the end user, the date-time value
                        should be expressed in terms of the time zone specified in the end user’s profile. If you are
                        processing a date-time value submitted by the end user, you need to be aware that the value is
                        specified in terms of the time zone specified in the end user’s profile.
                        Local machine. Date-time values that you get from business object fields are represented in
                        terms of the time zone of the local machine (the machine running the CBO application).
                        Database machine. Date-time values in the database are represented in terms of the master time
                        zone. (The master time zone is either the time zone of the database server machine or the time
                        zone specified by the Master Time Zone configuration item. For details on the master time
                        zone, see the manual System Administration Guide.)

                    Figure 15 summarizes the issues with the different time zones involved in CBO web applications.

Figure 15      Time zones in an environment where web applications run

                                                       End user’s machine            End user’s time zone
                                                                                     (time zone in the
                                                          Web browser                end user’s profile)
      Setting business object fields from
      script code:
      If you set a date-time field to a string                           Getting business object fields from script code:
      value, make sure that the value is                                 When getting the value of a date-time field that you
      expressed in terms of the end user’s                               want displayed to an end user, use the
      time zone.                                                         Field.ToString method.
      The business objects automatically                                 This method expresses the value in terms of the end
      convert the value from the end user’s                              user’s time zone.
      time zone to the local time zone.
      If you set a date-time field to a variant
      date, make sure that the value is                                                   Web server machine
      expressed in terms of the local time
      zone.                                              Web application
                                                                                     Local time zone
                                                                                     (the time zone of the
                                                                                     web server machine
                                                              CBO                    where the business
                                                                                     objects are used)


                                                        CBO Data Service

                                                                         The business objects internally handle
                                                                         conversions between the local time zone
                                                                         and the master time zone.

                                                                                  Database server machine

                                                                                     Master time zone
                                                                                     (the time zone that
                                                                                     date-time values are
                                                                                     stored in)
                                                       ClarifyCRM Database




102
                                                        CHAPTER 2   Querying and Updating Data in the Database




The following sections explain these issues in detail:
   Getting the Value of a Date-Time Field on page 103
   Setting the Value of a Date-Time Field on page 104
   Getting the Current Date and Time on page 105
   Calculating Date-Time Values on page 105
   Overriding Display in Master Time on page 106
   How Text Time Stamp Formats Affect CBO on page 107

Getting the Value of a Date-Time Field
When you get the value of a date-time field from a business object, you get a variant date expressed
in terms of the time zone of the local machine (for example, the time zone of the web server
machine). The business objects automatically convert date-time values from the master time zone
to the local time zone.

If you want to display this value to the end user, you need to express the value in terms of the end
user’s time zone and locale. To convert the value to a string expressed in these terms, use one of the
following methods:
   Bo.ToString or Field.ToString
   The Bo.ToString or Field.ToString methods use the short date format of the current
   session’s locale. (You can change this format by setting the
   Session.Item("Locale.ShortDateFormat") property.)
   Session.ToDateString
   The Session.ToDateString method gets the string representation of a value in a specified
   format. (You specify this format as arguments to this method.)

Both methods convert the date-time value from the local time zone to the end user’s time zone. The
Field.ToString method also formats the date-time value according to the end user’s locale.

Note: If the display_mstr_time_zone configuration item is present and the i_value field is set to 1,
the Field.ToString and Session.ToDateString methods express date-time values in terms of the
master time zone, not the end user’s time zone. See Overriding Display in Master Time on page 106.




                                                                                                          103
Working with Field Values and Data Types




                  For example, the following code displays a date-time value with the complete name of the month, a
                  two-digit day of the month, and a four-digit year. The statement displays 24-hour time.
                     In Java (JSP):
                     ...
                     <%@ page import="com.clarify.cbo.*" %>
                     ...
                     <%
                     // Convert the date-time value before displaying it.
                     String strDate =
                        (String) clfySession.toDateString(
                        (Calendar) boCase.getFields().getItem("modify_stmp").getValue(),
                        "MMMM dd, yyyy HH:mm:ss");
                     // Display the converted value.
                     %>
                     <P>Case Last Modified: <%=strDate%></P>
                     ...
                     In JavaScript (ASP):
                     ...
                     <%
                     // Convert the date-time value before displaying it.
                     var strDate = ClfySession.ToDateString(BoCase("modify_stmp").Value,
                        "MMMM dd, yyyy HH:mm:ss");
                     // Display the converted value.
                     %>
                     <P>Case Last Modified: <%=strDate%></P>
                     ...

                  Setting the Value of a Date-Time Field
                  When you set the value of a date-time field, the business objects assume the following:
                     If you set a field to the string representation of a date-time value, the business objects assume
                     that the value is expressed in terms of the end user’s time zone.
                     Internally, the business objects convert the value from the end user’s time zone to the master
                     time zone before storing the value in the database.
                     If you set a field to a variant date, the business objects assume that the value is expressed in
                     terms of the local time zone.
                     Internally, the business objects convert the value from the local time zone to the master time
                     zone before storing the value in the database.
                     If the variant date is not expressed in terms of the local time zone, use the
                     Session.ConvertTimeZone method to convert the value from its current time zone to the
                     time zone of the local machine.




104
                                                       CHAPTER 2   Querying and Updating Data in the Database




For example, the following section of code assembles a date-time value as a string in ISO format.
The code sets this string as the value of a business object field. The business object assumes that this
value is expressed in terms of the end user’s time zone and internally converts the string to a
date-time value in terms of the local time zone. Before committing this value to the database, the
business objects convert that date-time value to the master time zone.
   In Java:
   ...
   // Assemble the date-time value as a string.
   String strISODate = strYear + "-" + strMonth + "-" + strDay +
      " " + strHour + ":" + strMinute + ":" + strSecond;
   // Set the field to this string. The business object internally converts
   // between the end user’s time zone and the master and local time zones.
   boCase.getFields().getItem("hangup_time").setValue(strISODate);
   ...
   In JavaScript:
   ...
   // Assemble the date-time value as a string.
   var strISODate = strYear + "-" + strMonth + "-" + strDay + " " +
      strHour + ":" + strMinute + ":" + strSecond;
   // Set the field to this string. The business object internally converts
   // between the end user’s time zone and the master and local time zones.
   BoCase("hangup_time").Value = strISODate;

Getting the Current Date and Time
When you need to get the current date and time to generate a time stamp, you must make sure that
this date-time value is in sync with the clock on the database server. The current time on the web
server machine (the local machine) may differ from the current time on the server machine, even
when time zone differences are taken in to account. (For example, even if both machines are in the
same time zone, the clock on the web server may be a few minutes ahead or behind the clock on the
database server.)

Because of this potential difference between the clocks on these machines, do not use any built-in
script language functions (such as the JavaScript Date object constructor) to get the current date
and time. Instead, use the ClarifyCRM Session.GetCurrentDate method to get the current
time on the server machine in terms of the local machine’s time zone.

For example, the following statement sets the value of the hangup_time field in the Case
business object to the current date and time:
   In Java:
   boCase.getFields().getItem("hangup_time").setValue(
      clfySession.getCurrentDate());
   In JavaScript:
   BoCase("hangup_time").Value = ClfySession.GetCurrentDate();

Calculating Date-Time Values
When you need to calculate date-time values (for example, in order to get the difference between
two values or to add an offset to a date-time value), you should convert the values to GMT before
performing any calculations. The problem with calculating date-time values in other time zones is
that changes in daylight savings time are not taken in to account.


                                                                                                         105
Working with Field Values and Data Types




                  You can use the Session.ConvertTimeZone method to convert a binary date-time value from
                  one time zone to another. When specifying the time zones to use for conversion, you can use any
                  time zone listed in the time_zone table in addition to the following strings:
                     "Master" — the master time zone, which represents the time zone used for date-time values
                     stored in the database
                     "Local" — the time zone of the machine processing the business objects
                     “User” — the end user’s time zone, or the master time zone if your environment is configured
                     to display date-time values in terms of the master time zone (for details, see Setting the Locale
                     and Time Zone on page 272 and the manual System Administration Guide)

                  For example, the following code sets up a filter to find all cases created in the past two weeks:
                     In Java:
                     import com.clarify.cbo.*;
                     import java.util.*;
                     ...
                     Calendar calendar_obj = Calendar.getInstance();
                     calendar_obj = (Calendar) clfySession.convertTimeZone("Local",
                        calendar_obj, "GMT");
                     calendar_obj.add(Calendar.DATE, -14);
                     calendar_obj = (Calendar) clfySession.convertTimeZone("GMT",
                        calendar_obj, "Local");
                     String strQueryDate = clfySession.toSQLString(calendar_obj,
                        CboConstants.cboSQLQuote, CboConstants.cboTypeDateTime);
                     boCase.setFilter("creation_time > " + strQueryDate);
                     In JavaScript:
                     ...
                     var varDateCurrent, dGMTDate, dTwoWeeksAgo, varDateTwoWeeksAgo,
                        nMsecInDay, strQueryDate;
                     // Get the current date and time in GMT.
                     varDateCurrent = ClfySession.GetCurrentDate();
                     dGMTDate = new Date(ClfySession.ConvertTimeZone("Local",
                        varDateCurrent, "GMT"));
                     // Calculate the date and time two weeks ago.
                     nMsecInDay = 1000 * 60 * 60 * 24;
                     dTwoWeeksAgo = new Date(dGMTDate.valueOf() - nMsecInDay * 14);
                     // Convert the calculated date-time value to the local time zone.
                     varDateTwoWeeksAgo = ClfySession.ConvertTimeZone("GMT",
                        dTwoWeeksAgo.getVarDate(), "Local");
                     // Convert the date-time value to the master time zone, formatting
                     // the value for use in a search filter.
                     strQueryDate = ClfySession.ToSQLString(varDateTwoWeeksAgo);
                     boCase.Filter = "creation_time > " + strQueryDate;

                  When comparing date-time values in Visual Basic, always use the DateDiff function. If you use
                  the = operator, you may not get a precise comparison between values.

                  Overriding Display in Master Time
                  If your database contains the configuration item named display_mstr_time_zone and the
                  i_value field of this configuration item is set to 1, the ClarifyCRM client displays date-time
                  values in terms of the master time zone, not the time zone of the machine running the ClarifyCRM
                  client. (For details on this configuration item, see the manual System Administration Guide.)


106
                                                     CHAPTER 2   Querying and Updating Data in the Database




This configuration item also affects the way that the string conversion methods work in CBO
applications. If this configuration item is present and the i_value field is set to 1, the
Field.ToString, Session.ToString, and Session.ToDateString methods all return
string representations expressed in terms of the master time zone (not the end user’s time zone).

To override this (so that the returned strings express values in terms of the end user’s time zone),
set the Session.Item("Misc.DisableMasterTimezoneDisplay") property to True.

How Text Time Stamp Formats Affect CBO
In some situations, time stamps are embedded within string fields and stored in the database. For
example, the case_history field in the case table contains time stamps:
*** NOTES 05/02/2000 07:20:56 Pacific Standard Time [05/02/2000 04:20:56 Pacific
Standard Time PST] username Action Type:Manager review
(logged note)

The following configuration items determine how the ClarifyCRM client formats these time stamps
before embedding them in text. (These items are explained in the System Administration Guide.)
   date_time entry
   This configuration item specifies whether to use the local machine’s regional settings, the
   DD-MMM-YYYY format, or a custom format (specified by the local_time format and
   site_time format configuration items).
   This configuration item also specifies if the converted time stamp should include the time in
   terms of the customer site’s time zone (as well as the time in terms of the local time zone).
   local_time format
   If the date_time entry configuration item specifies that a custom format should be used,
   this configuration item specifies this format for time stamps expressed in the local time zone.
   site_time format
   If the date_time entry configuration item specifies that a custom format should be used,
   this configuration item specifies this format for time stamps expressed in the customer site’s
   time zone.

The business objects also use these configuration items when formatting time stamps to be
embedded in text:
1. If the date_time entry configuration item is present and specifies that a custom format
   should be used for local time, the business objects use the format specified by the local_time
   format configuration item.
   If the local_time format configuration item is not present, the business objects use the
   format YYYY/MM/DD HH:MM:SS (where time is expressed in 24-hour format).
2. If the date_time entry configuration item is present and specifies that a standard format
   should be used for local time, the business objects use the format dd-MMM-yyyy HH:mm:ss.
3. If the date_time entry configuration item is present and specifies that the regional settings
   of the local machine should be used, the business objects use the short date format of the current
   locale.

The business objects use a similar algorithm to determine the format for site time (if the
date_time entry configuration item specifies that site time should be included in the converted
time stamp).


                                                                                                       107
Getting Metadata for Your Application




                  For example, when you use the NotesLog business object to log a note to a case, the business
                  object gets the current date and time and generates an entry in the case history. When formatting
                  this date-time value as text, the business object uses the algorithm previously outlined.

                  Specific types of business objects (such as the NotesLog business object) have application code for
                  formatting time stamps and updating the case history. The Generic business object does not have
                  this application code. If you are using a Generic business object to update the case history, you
                  need to write your own code to convert time stamps to the appropriate format.

                  If you want to get the format that the business objects use to convert time stamps to text, get the
                  ClarifyCRM Session.Item("Misc.User Time Format") and the
                  Session.Item("Misc.Site Time Format") properties.




Getting Metadata for Your Application
                  Some of the data in the ClarifyCRM database is used by the ClarifyCRM client to fill pop-up menus
                  and display strings (such as labels and error messages).

                  If you are developing the user interface for your own application, you can use the business objects
                  to access this application metadata in order to display pop-up menus, labels, and error messages.

                  The CBO Data Service reads this metadata from the database and keeps the metadata in a cache.
                  You can access data for pop-up lists and strings by using the ChoiceList object (which represents
                  a pop-up list), the Choice object (which represents an item in a pop-up list), and the Session
                  object (which provides access to strings in the string_db table). You do not need to query the
                  database to get this data.

                  The following sections explain how to use these objects:
                      Working with ClarifyCRM Choice and User Choice Lists on page 108
                      Working with Strings from the string_db Table on page 123
                      Updating Cached Metadata on page 123

                  For details on the metadata cached by the CBO Data Service, see the System Administration Guide.


                  Working with ClarifyCRM Choice and User Choice Lists
                  CBO provides special utility objects for creating and manipulating lists of choices, such as
                  application pop-up lists. These utility objects are described in the following sections:
                      Background: Lists of Choices in ClarifyCRM Applications on page 109
                      How CBO Applications Access Lists of Choices on page 112
                      Using Choice Object and Choice Fields on page 112
                      Example of Displaying Priority, Severity, Type on page 114
                      Using the ChoiceList Object on page 115
                      Examples of Displaying the Available Choices on page 118
                      Setting the Values of Choice Fields on page 120
                      Use of Reference Id for Choice Objects on page 122


108
                                                        CHAPTER 2   Querying and Updating Data in the Database




Background: Lists of Choices in ClarifyCRM Applications
Some of the forms in the ClarifyCRM client application contain lists of choices from which the end
user can select. Examples of lists of choices include:
   Application pop-up lists and user-defined pop-up lists
   The list of status codes for a given condition
   The list of states or provinces in a given country
   The list of channels for a given medium
   The list of countries in the database
   The list of time zones available in the database

The application constructs these lists from data in various database tables. For example, the
ClarifyCRM client application builds an application pop-up list (ClarifyCRM choice list) from a
row in the gbst_lst table (which represents the pop-up list) and rows in the gbst_elm table
(which represent items in the pop-up list). The application uses the gbst_elm2gbst_lst relation
between gbst_elm rows and gbst_lst rows to determine which items belong to a given pop-up
list. See ClarifyCRM Choice Lists for more on ClarifyCRM choice lists.

The fields that store the selected item from a choice list are called choice fields. There are specific
APIs in ClarifyCRM Business Objects to support choice fields. See the Core Business Objects
Reference Guide for information on these APIs. Each choice field is described by metadata stored in a
row in the choice_fld table. This metadata is provided out of box for all choice fields, except for
the majority of user-defined choice fields (choice fields storing selections from user-defined lists).
The Usage Information section in the Choicelist Administration screen of ClarifyCRM
Administrator displays this information and allows you to add configuration information for
user-defined choice fields.

When an end user selects and saves a choice, the choice is stored in a choice field. A user choice
field can be represented by a relation to the row representing the choice, a copy of the text of the
choice, or both the relation and a copy of the text.

ClarifyCRM Choice Lists
Figure 16 illustrates how the ClarifyCRM client application uses rows in the gbst_elm and
gbst_lst tables to display application pop-up lists for case priority and case severity. When the
end user selects a case priority, the relation to the selected priority is stored in the case table. (The
object ID of the related gbst_elm row is stored in the respprty2gbst_elm field.)




                                                                                                          109
Getting Metadata for Your Application




Figure 16     How the ClarifyCRM client uses the gbst_elm and gbst_lst tables to display lists of choices
Each row in the gbst_lst table                        gbst_lst table
specifies a list of choices.                                                                                  Priority:
                                          title                        objid                                                 No Rush
                                                                                                                               Low
                                          Response Priority Code       268435457                                             Medium
                                          Problem Severity Level       268435458                                               High
                                                                                                                              Urgent

                                                          gbst_elm table

                                          objid        title        gbst_elm2gbst_lst
            case table
                                          268435457    No Rush 268435457                              Related gbst_elm
   respprty2gbst_elm        ...           268435458    Low          268435457                         rows identify the
                                                                                                      choices in the list.
   268435459                ...           268435459    Medium 268435457

 The selected choice is stored as a       268435460    High         268435457
 relation from the case table to the      268435461    Urgent       268435457
 gbst_elm table.
                                          268435463    No Rush 268435458
                                          268435464    Low          268435458
                                          268435465    Medium 268435458
                                          268435466    High         268435458
                                          268435467    Urgent       268435458



                  User Choice Lists
                  ClarifyCRM applications have user-defined pop-up lists (user choice lists) that you can configure
                  in Policies and Customers or the Choicelist administrator in the ClarifyCRM Administrator for
                  ClarifyCRM 13.0. These are the only choice lists that you can create. Some of these lists are defined
                  out of the box. You can use these user choice lists to define both single level and hierarchical lists.
                  For example, when you select a product family in the Product Family pop-up list, the Product Line
                  pop-up list displays product lines specific to that family.

                  In the ClarifyCRM database, these user choice lists are stored in the hgbst_elm, hgbst_lst, and
                  hgbst_show tables. Each row in the hgbst_lst table represents a user choice list, and each row
                  in the hgbst_elm table represents an item in a list. The hgbst_show table specifies the hierarchy
                  of pop-up lists.

                  A choice field can store the user's selection in three different ways:
                  Table 7         How a selected choice is stored

                   Selection Stored as Text         Selection Stored as Relation                Selection Stored as Both Text
                   Field                                                                        Field and Relation
                   Choice Title is stored in        Foreign key to choice object ID is stored   Both Choice Title and Relation
                   choice field. Prior to           in choice field. This is how all non-user   are stored in separate but
                   ClarifyCRM 13.0, this is         choice fields are stored. Beginning in      coordinated columns. This
                   how user-defined choices         ClarifyCRM 13.0, user choice fields may     configuration is used for
                   were always stored.              also be configured this way.                compatibility with the
                                                                                                ClarifyCRM Client.




110
                                                           CHAPTER 2   Querying and Updating Data in the Database




Table 7      How a selected choice is stored (Continued)

Selection Stored as Text        Selection Stored as Relation             Selection Stored as Both Text
Field                                                                    Field and Relation
Bits 2 and 3 of flags column Bits 2 and 5 of flags column in choice_fld Bits 2 and 4 of flags column in
in choice_fld table turned table turned on.                             choice_fld table turned on.
on.
  field_name column of            The rel_name is populated with            The field_name column of
  the choice_fld table            the foreign key relation column           the choice_fld table
  contains the target table       name of the target table.                 contains the target table
  column name that                                                          column name that receives
  receives the                                                              the hgbst_elm.title
  hgbst_elm.title                                                           value.
  value.
                                                                            The rel_name is populated
                                                                            with the foreign key relation
                                                                            column name of the target
                                                                            table.


One other major difference between application pop-up lists and user-defined pop-up lists is that
user-defined pop-up lists can be hierarchical. Hierarchical lists can also be ragged lists, where some
branches in the hierarchy have a greater depth than others.

Figure 17 illustrates how selections from user choice lists are stored as text fields, as relations, and
as both text fields and relations.

Figure 17     How selections for user choice lists are stored in the ClarifyCRM database
    hgbst_elm table
                                 Example of the selected choice stored as a
                                 relation
     objid           title
     268435545       Choice 1                         my_custom table
     268435546       Choice 2      Choice 1
                                   Choice 2            my_cust2hgbst_elm
     268435547       Choice 3
                                   Choice 3            268435459


    hgbst_elm table                Example of the title of the selected choice stored as
                                   text in a field
    objid           title
                                                      part_num table
    268435545       Choice 1
    268435546       Choice 2
                                    Choice 1
                                                      my_choice
                                    Choice 2
    268435547       Choice 3        Choice 3          Choice 2



    hgbst_elm table                Example of the title of the selected choice stored in
                                   both a text field and a relation
     objid          title
                                                      my_custom table
     268435545      Choice 1
     268435546      Choice 2
                                    Choice 1
                                                      my_choice        my_cust2hgbst_elm
                                    Choice 2
     268435547      Choice 3        Choice 3          Choice 2         268435546



                                                                                                             111
Getting Metadata for Your Application




                  How CBO Applications Access Lists of Choices
                  The CBO Data Service and the business objects automatically load and cache the rows representing
                  choices and lists of choices (such as rows from the gbst_elm and gbst_lst tables). In your CBO
                  application, you can access this cached data through Choice objects and ChoiceList objects. You
                  do not need to set up your own business objects to query the database for these rows.

                  Note: If you add any data to the tables used by choice lists and if any CBO applications are currently
                  running, you need to update the metadata caches in order for the new choice list data to be available to your
                  CBO applications. For details on metadata caches, see the System Administration Guide.


                  In addition to application pop-up lists and user choice lists, Choice and ChoiceList objects
                  represent other kinds of lists of choices. These include the list of countries in the database, the list of
                  states or provinces in a country, and the list of status codes for a condition. For information on the
                  types of choices represented by Choice and ChoiceList objects, see the Core Business Objects
                  Reference Guide.

                  Using Choice Object and Choice Fields
                  A ClarifyCRM Choice object represents an item in a list of choices. Each Choice object provides
                  you with the necessary data for presenting the choice to an end user, including:
                        The displayed title of the item
                        The description of the item
                        The rank or order of the item in the list
                        The state of the item (whether the item is active or inactive)

                  To access the Choice object that represents the currently selected choice, you use a choice field.
                  The value of a choice field is the object ID of the related choice.

                  For example, a business object for the case table has choice fields named priority, severity,
                  and case_type. These fields represent the choices (the gbst_elm rows) related to the case
                  through the respprty2gbst_elm, respsvrty2gbst_elm, and calltype2gbst_elm
                  relations. The values of these choice fields are the object IDs of the related gbst_elm rows.

                  As is the case with other Field objects, you access a choice field by specifying the name of the field
                  in the Fields collection. (Choice field names are defined in the choice_fld table. For a complete
                  listing of choice fields, see the Core Business Objects Reference Guide. For information on accessing
                  fields in a business object, see Using the Field Object to Get Data from the Row on page 55.)

                  Choice fields are available in Generic and specific types of business objects. For example, the
                  priority, severity, and case_type fields are available in Case business objects as well as
                  in Generic business objects for the case table.

                  You can use the Field.Choice property to get a Choice object for a choice field. Table 8 lists
                  some of the types of information that you can get from the Choice object.
                  Table 8     Types of information accessible from Choice objects
                   Property in          Description
                   Choice Object
                   Id                   Object ID of the related row (for example, object ID of the related gbst_elm row)
                   Title                Displayed title of the item in the list (for example, the value of the gbst_elm.title
                                        field)


112
                                                                         CHAPTER 2   Querying and Updating Data in the Database




Table 8        Types of information accessible from Choice objects
Property in           Description
Choice Object
Description           Description of the item (for example, the value of the gbst_elm.description field)
Rank                  Rank or order of the item in the list (for example, the value of the gbst_elm.rank field)
State                 State (active, inactive, or default) of the item (for example, the value of the
                      gbst_elm.state field)


For example, the following JavaScript statement gets the currently selected priority of the case:
strPriority = BoCase("priority").Choice.Title;

Figure 18 illustrates how choice fields work.

Figure 18       Example of the priority choice field in a business object for the case table
Currently selected row           Choice field

                                  BoCase (a Case business object)

     id_number         priority              ...                    Title: Medium
                                                                        Rank: 2
     92                (Choice Object) ...                                 ...

                      Rowset                                        Choice object
   BoCase("priority").Value: 268435459 (object ID of related row in gbst_elm)
   BoCase("priority").Choice: Choice object for the related row in gbst_elm
   BoCase("priority").Choice.Title: Medium
   BoCase("priority").Choice.Rank: 2
   BoCase("priority").Choice.Default: Choice object for default gbst_elm row (state = 2)



                         case table

          id_number      respprty2gbst_elm ...
          92             268435459                     ...


                                    gbst_elm table

          objid          title        rank         state     gbst_elm2gbst_lst
          268435459      Medium 2                  2         268435457                             Currently
                                                                                                   selected
                                                                                                   choice
                                 ClarifyCRM database


As discussed in User Choice Lists on page 110, user choice fields may be configured to store the
user's selection as a text field, relation, or both. In the case where both a text field and relation are
stored, there are two separate Field objects defined. In the case where a user-defined choice field
has been configured to store both a text field and a relation, the Value property (the setValue
method in Java) automatically updates one when the other is set.




                                                                                                                           113
Getting Metadata for Your Application




                  Example of Displaying Priority, Severity, Type
                  In the case table, the case priority, severity, and type are represented by relations to rows in the
                  gbst_elm table. In the Case business object (or in a Generic business object for the case table),
                  these relations are represented by the priority, severity, and case_type choice fields. You
                  can use the choice fields to get the associated Choice objects, and you can use the Choice objects
                  to get the titles of the related gbst_elm rows.

                  The following example displays the case type, priority, and severity in an HTML table. In this
                  example, BoCase is a Case business object with the DataFields property set to "*".
                      In Java (JSP):
                      <%@ page import="com.clarify.cbo.*" %>
                      ...
                      <P>Cases Found: <%=boCase.getCount()%></P>
                      <TABLE BORDER=1>
                         <TR>
                            <TH>Case ID</TH>
                            <TH>Type</TH>
                            <TH>Priority</TH>
                            <TH>Severity</TH>
                         </TR>
                         <% // Iterate through each record in the results.
                         // Get the case ID, type, priority, and severity.
                         for (nRows = 1; nRows < boCase.getCount() + 1; nRows++) { %>
                            <TR>
                            <TD><%=boCase.getFields().getItem("id_number").getValue()%>
                               &nbsp;</TD>
                            <TD><%=boCase.getFields().getItem(
                               "case_type").getChoice().getTitle()%>
                               &nbsp;</TD>
                            <TD><%=boCase.getFields().getItem(
                               "priority").getChoice().getTitle()%>
                               &nbsp;</TD>
                            <TD><%=boCase.getFields().getItem(
                               "severity").getChoice().getTitle()%>
                               &nbsp;</TD>
                         </TR>
                         <% // Move to the next record in the results.
                         boCase.moveNext(); %>
                      </TABLE>
                      In JavaScript (ASP):
                      <P>Cases Found: <%=BoCase.Count%></P>
                      <TABLE BORDER=1>
                      <TR>
                         <TH>Case ID</TH>
                         <TH>Type</TH>
                         <TH>Priority</TH>
                         <TH>Severity</TH>
                      </TR>
                      <% // Iterate through each row in the results. Get the case ID for each
                      // row. In addition, use Choice objects to get the case type,
                      // priority, and severity.
                      var nCount = BoCase.Count;
                      for (nRows = 1; nRows <= nCount; nRows++) { %>
                         <TR>


114
                                                       CHAPTER 2   Querying and Updating Data in the Database




      <TD><%=BoCase("id_number")%>&nbsp;</TD>
      <TD><%=BoCase("case_type").Choice.Title%>&nbsp;</TD>
      <TD><%=BoCase("priority").Choice.Title%>&nbsp;</TD>
      <TD><%=BoCase("severity").Choice.Title%>&nbsp;</TD>
      </TR>
      <% // Move to the next row in the results.
      BoCase.MoveNext() %>
   </TABLE>

The following is an example of the results generated by this page:
Case ID      Type          Priority         Severity
=======      ====          ========         ========
1            Problem       Medium           Medium
2            Problem       High             High
3            Question      Low              Low

Using the ChoiceList Object
A ClarifyCRM ChoiceList object represents a list of items to choose from. Each ChoiceList
object provides you with the necessary data to present a list of choices to an end user. You can use a
ChoiceList object to do the following:
   Access each choice in a list as a Choice object
   Access the choices by their rank in the list
   Access the default choice for the list

For example, the ChoiceList object for the Response Priority Code application pop-up list
provides access to the gbst_lst row representing this pop-up list.

Each ChoiceList object has a name that specifies the list that the object represents. For example,
if the ChoiceList object represents an application pop-up list, the name of the ChoiceList
object is the name of the application pop-up list. (Exceptions to this rule are the ChoiceList
objects that represent the lists of countries and time zones in the database.)

Different types of ClarifyCRM choice lists are defined in the list_struct table. (For example,
one row in this table defines the type of choice list that represents data in the gbst_elm and
gbst_lst tables, such as application pop-up lists and lists of status codes.)

To get a choice list for a given choice field, you can do one of the following:
   To get the choice list that contains the currently selected choice, get the value of the
   Field.ChoiceList property.
   You can also get the ChoiceList object through the name of the choice list with the currently
   selected choice. Use the Field.ChoiceListName property to get the name of the choice list
   for the currently selected choice, and use this name in the Field.ChoiceListByName
   property to get that choice list. (This is equivalent to getting the Field.ChoiceList property.)
   Both techniques use the currently selected choice to find the corresponding choice list. If the
   current value of the field is NULL or empty, both Field.ChoiceList and
   Field.ChoiceListName properties raise an error.
   Normally, choice fields should not be NULL. By default, when you call the BO.AddNew method
   to add a new row to the rowset, any choice fields are automatically filled with the default choice
   in the default choice list for that field.




                                                                                                         115
Getting Metadata for Your Application




                      To get the default choice list for a choice field (for example, if the field has a NULL value and you
                      do not know the name of the choice list that you want to get), use the
                      Field.DefaultChoiceListName property to get the name of the default choice list, and use
                      this name in the Field.ChoiceListByName property to get that choice list.
                      Unlike the Field.ChoiceList and Field.ChoiceListName properties, the
                      Field.DefaultChoiceListName property does not rely on the current value of the field to
                      find the choice list name. In situations where the value of the field is NULL or empty, this
                      property does not throw an exception. Instead, this property relies on metadata about the choice
                      field.
                      To get the choice list with a specific name, use the Field.ChoiceListByName property.
                      In general, you should not need to use this technique if you are getting an application pop-up
                      list or lists of all countries or time zones in the database. In these situations, the choice field
                      should have a currently selected choice (or a default choice) that has an associated choice list.
                      You need to use this technique if you do not want to get the default choice list or the choice list
                      associated with the currently selected choice. For example, you should use this technique if:
                      – the choice list represents a list of states or provinces in a country, and you want to present the
                        end user with the list for a different country (not the default country or the country associated
                        with the currently selected state or province)
                      – the choice list represents a list of channels for a medium, and you want to present the end
                        user with the list for a different medium (not the default medium or the medium associated
                        with the currently selected channel)
                      For example, in ClarifyCRM eResponse Manager, you use Communication business objects to
                      create new rows in the communication table for incoming email messages. For the channel
                      choice field in this business object, the default choice list is the list of channels for the generic
                      medium. (The name of the default choice list is Generic.)
                      Since you are creating new rows for email messages, you should get the choice list of channels
                      for the email medium. Using the Field.ChoiceListByName property, get the choice list
                      named E-mail, and use that list to select the email channel for the incoming email message.

                  Figure 19 illustrates how to access the choice list for a given choice field. Note in this example that
                  the choice list associated with the currently selected choice is not the default choice list for this
                  field. (The default list is the list of states and provinces in the USA and Canada. The currently
                  selected choice is Quebec, which belongs to the list for Canada.)




116
                                                                                CHAPTER 2   Querying and Updating Data in the Database




Figure 19   Examples of accessing the choice list for a choice field

   Currently selected row             Choice field

                                          BoAddress (a business object for the address table)

        state_prov          ...
                                     Title: QB                                                           Title: AB
        (Choice Object) ...                                                  Name: Canada                Description: Alberta
                                     Description: Quebec
                                                                            ChoiceList object
               Rowset                       Choice object                   containing the               Title: ...
                                                                            currently selected           Description: ...
        Name: USA                                                           Choice object
       Default ChoiceList object for the                                                                Other Choice objects in
       state_prov choice field                                                                          the ChoiceList object

                      BoAddress("state_prov").Value: 268435584
                      BoAddress("state_prov").Choice: Choice object for the related row in state_prov
                      BoAddress("state_prov").ChoiceList: ChoiceList object for that Choice object
                      BoAddress("state_prov").ChoiceListName: Canada
                      BoAddress("state_prov").DefaultChoiceListName: USA


                                                            ClarifyCRM database

            address table                                    state_prov table                                  country table

     address2state_prov       ...   objid            name     description      state_prov2country         objid         name
     268435584                ...   268435584        QB       Quebec           268435528                  268435528     Canada
                                    268435580        AB       Alberta          268435528
      Currently selected            ...              ...      ...              268435528
                  choice


                                                               Other choices in                     List of choices containing
                                                               the list                             currently selected choice

                 Once you get the ChoiceList object for a field, you can use the methods and properties of this
                 object to get the choices to display to the end user:
                     To determine the number of choices in the list, use the ChoiceList.Count property.
                     To get the default choice in the list as a Choice object, use the ChoiceList.Default
                     property.
                     To get the choices in the link in order of rank, use the ChoiceList.GetChoiceByRank
                     method. Pass the rank of the item to this method.
                     To get an individual choice in the list, use the ChoiceList.Item property.
                     Note that ranks are 0-based, while the ChoiceList.Item property is 1-based.

                 As mentioned in Using Choice Object and Choice Fields on page 112, once you get a Choice object,
                 you can then use the methods and properties of that object to display the choice in the list.

                 If the choice field is currently associated with an inactive choice, the inactive choice is included in
                 the choice list returned by Field.ChoiceList.




                                                                                                                                  117
Getting Metadata for Your Application




                  Examples of Displaying the Available Choices
                  The following example demonstrates how to display an HTML <SELECT> element for selecting
                  the case priority for a new case. The example does the following:
                      Gets the ChoiceList object for the priority choice field
                      Uses the associated Choice objects to set up the options in the HTML <SELECT> element
                      Sets up the default choice in the choice list as the selected option in the HTML <SELECT>
                      element (since this applies to a new case)
                      Sets the value of each option to the object ID of the corresponding Choice object (use the object
                      ID to set the selected choice in a choice field; see Setting the Values of Choice Fields on page 120)

                  Note: The object ID of a choice is a string value. In Java, when you compare object IDs, make sure to use the
                  String.equals method instead of the == operator. (The == operator determines if the two variables are
                  references to the same String object. This operator does not compare the actual string values.)


                  In this example, BoCase is a Case business object with the DataFields property set to *.
                      In Java (JSP):
                      <%@ page import="com.clarify.cbo.*" %>
                      ...
                      Choice objChoice = null;
                      String strChoiceId = null;
                      <SELECT NAME="case_type">
                      <% // Get the ChoiceList object for the "case type" choice field, and
                      // get each Choice object associated with the ChoiceList object.
                      ChoiceList colCaseType = boCase.getFields().getItem(
                         "case_type").getChoiceListByName(boCase.getFields().getItem(
                         "case_type").getDefaultChoiceListName());
                      for (int nRank = 0; nRank < colCaseType.getCount(); nRank++) {
                         // Use each Choice object to fill the HTML Select control. Use the
                         // Title property to display the title of the option, and use the
                         // Id property as the value of the option.
                         objChoice = colCaseType.getChoiceByRank(nRank);
                         strChoiceId = colCaseType.getDefault().getId();
                         %> <OPTION <%
                         // If the Choice object is the default choice for the list, set
                         // the choice as the currently selected option in the Select control.
                         if (strChoiceId.equals(objChoice.getId())) {%>
                            SELECTED = TRUE
                         <% } %>
                         VALUE="<%=objChoice.getId()%>">
                         <%=objChoice.getTitle()%></OPTION>
                      <% } %>
                      </SELECT>




118
                                                CHAPTER 2   Querying and Updating Data in the Database




   In JavaScript (ASP):
   <SELECT NAME="case_type">
   <% // Get the ChoiceList object for the "case type" choice field, and
      // get each Choice object associated with the ChoiceList object.
      var colCaseType = BoCase("case_type").ChoiceListByName(
         BoCase("case_type").DefaultChoiceListName)
      // Use the ChoiceList.Default property to get the object ID of
      // the default choice for this list.
      var numDefaultCaseType = colCaseType.Default.Id
      // Use each Choice object to fill the HTML <SELECT> element.
      // Use the Title property to display the title of the option,
      // and use the Id property as the value of the option.
      // Note that because the script uses the rank to get each choice,
      // the counter starts from 0 instead of 1.
      for (nRank = 0; nRank < colCaseType.Count; nRank++) {
         // Get the Choice object.
         objChoice = colCaseType.GetChoiceByRank(nRank);
         %> <OPTION
         // Set up the default choice as currently selected.
         <% if (numDefaultCaseType == objChoice.Id){ %>
            SELECTED = TRUE
         <% } %>
         VALUE="<%=objChoice.Id%>">
            <%=objChoice.Title%>
         </OPTION>
   <% } %>
   </SELECT>

The following HTML is generated from this example:
<SELECT NAME="case_type">
   <OPTION
   VALUE="268435470">
   Administrative</OPTION>

   <OPTION
   VALUE="268435471">
   CR</OPTION>

   <OPTION
   VALUE="268435473">
   Marketing Information</OPTION>

   <OPTION
   SELECTED = TRUE
   VALUE="268435468">
   Problem</OPTION>

   <OPTION
   VALUE="268435469">
   Question</OPTION>

   <OPTION
   VALUE="268435472">
   Training Required</OPTION>
</SELECT>




                                                                                                  119
Getting Metadata for Your Application




                  If you want to display an HTML <SELECT> element for an existing case (in which a selection has
                  already been made), you can use the Field.Choice property to determine which choice is
                  currently selected for the case.
                      In Java (JSP):
                      <%@ page import="com.clarify.cbo.*" %>
                      ...
                      ChoiceList colCaseType = boCase.getFields().getItem(
                         "case_type").getChoiceListByName(boCase.getFields().getItem(
                         "case_type").getDefaultChoiceListName());
                      String strChoiceId = boCase.getFields().getItem(
                         "case_type").getChoice().getId();
                      ...
                      <OPTION <%
                      // Set up the current value of the field as the selected choice in the
                      // HTML <SELECT> element.
                      if (strChoiceId.equals(colCaseType.getItem(nChoices).getId())) {%>
                         SELECTED = TRUE
                      <%}%>
                      VALUE="<%=colCaseType.getItem(nChoices).getId()%>">
                      <%=colCaseType.getItem(nChoices)%>
                      </OPTION>

                      Note: The object ID of a choice is a string value. In Java, when you compare object IDs, make sure to use
                      the String.equals method instead of the == operator. (The == operator determines if the two
                      variables are references to the same String object. This operator does not compare the actual string
                      values of the two variables.)

                      In JavaScript (ASP):
                      var colCaseType = BoCase("case_type").ChoiceListByName(
                         BoCase("case_type").DefaultChoiceListName)
                      ...
                      <OPTION <%
                      // Set up the current value of the field as the selected choice in the
                      // HTML <SELECT> element.
                      if (BoCase("case_type") == colCaseType(nChoices).Id) {%>
                         SELECTED = TRUE
                      <%}%>
                      VALUE="<%=colCaseType(nChoices).Id%>">
                      <%=colCaseType(nChoices)%>
                      </OPTION>

                  Setting the Values of Choice Fields
                  To change the selected choice for a choice field, you need to set the value of the choice field to the
                  object ID of the new choice.

                  For a choice that is not a user-defined choice field configured to store selections as a text field, to
                  change the selected choice for a choice field, you need to set the value of the choice field to the
                  object ID of the new choice. For a user-defined choice fields configured to store selections as a text
                  field, you need to set the value of the choice field to the Title of the new choice.

                  In HTML, you can use <SELECT> elements to display a list of options. If you set the value of each
                  option to the object ID of the corresponding Choice object, you can use the submitted value of the
                  <SELECT> element to set the value of the choice field.


120
                                                     CHAPTER 2   Querying and Updating Data in the Database




In the previous section Examples of Displaying the Available Choices on page 118, the sample code
demonstrated how to set up the options to submit the object ID of the Choice object as the value of
the form element named case_type.

The following section of code gets this submitted value and changes the selected choice for the
choice field:
   In Java (JSP):
   <%@ page import="com.clarify.cbo.*, com.clarify.common.*" %>
   ...
   <jsp:useBean id="cboWebSupStr" scope="session"
      class="com.clarify.common.CboWebSupStr" />
   ...
   strCaseTypeObjid =
      cboWebSupStr.getParameter(request, session, "case_type");
   BoCase.getFields().getItem(
      "case_type").setValue(strCaseTypeObjid);
   ...
   In JavaScript (ASP):
   ...
   strCaseTypeObjid = Request("case_type").Item;
   BoCase.Fields("case_type").Value = strCaseTypeObjid;
   ...

Example of working with a hierarchical pop-up list
To get the ChoiceList object for the next pop-up list in the hierarchy, use a
Choice.ChildChoiceList property.

For example, suppose you have a ChoiceList object that represents the list of product families in
the FAMILY pop-up list. You can use the Choice object for a given family to get the ChoiceList
object that contains the list of product lines under that family.

The following example displays the product families and their lines in an HTML table:
   In Java (JSP):
   <%@ page import="com.clarify.cbo.*" %>
   <% int nFamilies, nLines;
   Choice Family;
   ChoiceList FamilyList, LineList;
   FamilyList = ClfySession.getUserChoiceListByName("FAMILY"); %>
   ...
   <TABLE BORDER=1>
      <TR>
         <TH>Product Family</TH>
         <TH>Product Line</TH>
      </TR>
   <% for (int nFamilies=1;nFamilies<=FamilyList.getCount();nFamilies++) {
         Family = FamilyList.getItem(nFamilies);%>
         <TR>
            <TD VALIGN=TOP><%=Family.getTitle()%></TD>
            <TD VALIGN=TOP>
            <% LineList = Family.getChildChoiceList();
            if (LineList != null) {
               for (int nLines=1; nLines<=LineList.getCount(); nLines++) {%>
                  <%=LineList.getItem(nLines).getTitle()%><BR>


                                                                                                       121
Getting Metadata for Your Application




                                  <%}
                               }%>
                            </TD>
                         </TR>
                      <% } %>
                      </TABLE>
                      In JavaScript (ASP):
                      <% var nFamilies, nLines, Family;
                      var FamilyList, LineList;
                      FamilyList = ClfySession.GetUserChoiceListByName("FAMILY"); %>
                      ...
                      <TABLE BORDER=1>
                         <TR>
                            <TH>Product Family</TH>
                            <TH>Product Line</TH>
                         </TR>
                      <% for (nFamilies = 1; nFamilies <= FamilyList.Count; nFamilies++) {
                         Family = FamilyList(nFamilies); %>
                         <TR>
                            <TD VALIGN=TOP><%=Family%></TD>
                            <TD VALIGN=TOP>
                               <% LineList = Family.ChildChoiceList;
                               if (LineList != null) {
                                  for (nLines = 1; nLines <= LineList.Count; nLines++) {%>
                                     <%=LineList(nLines)%><BR>
                                  <%}
                               }%>
                            </TD>
                         </TR>
                      <% } %>
                      </TABLE>

                  The following is an example of the results generated by this code:
                  Product Family             Product Line
                  ==============             =============
                  All                        All
                  Hardware                   PC Hardware
                                             Server Hardware
                                             All
                  Software                   All
                                             Operating Systems

                  Use of Reference Id for Choice Objects
                  Each ClarifyCRM choice object is associated with a reference Id which is a string that is used to
                  identify a Choice object (or the top level of the choice list). The string is unique per list. Reference
                  Ids provide a non translatable key the presence of which makes supporting upgrading choice and
                  choice list items easier. Reference Ids also provide an easy way to access a choice or choicelist item
                  through localizations and internationalization by providing a unique non-translatable identifier for
                  each item.

                  You can use the ClarifyCRM Administrator to add a reference Id to a choice list element. The choice
                  reference ID is stored in the ref_id field of the element table (e.g. table_gbst_elm). Choice list
                  reference IDs are stored in the ref_id field of the list table (e.g. table_gbst_lst). See the ClarifyCRM
                  Administrator Help for more details on using the ClarifyCRM Administrator.


122
                                                       CHAPTER 2   Querying and Updating Data in the Database




From the API, you can use the RefId property of the Choice or ChoiceList object to access this
string. You can also access a Choice object whose reference Id is available to you, by using the
GetChoiceByRefId method. A ChoiceList can be accessed using the ChoiceListByRefId
method.

Choice Lists and Status Transitions
Choice fields can be set up as status transition fields. Please see the System Administration Guide for
more details on how to accomplish this.


Working with Strings from the string_db Table
The ClarifyCRM client uses some of the strings in the string_db table to display error messages
or labels to the end user. Because the strings are read from this table and are not explicitly specified
in the application code, the strings can be easily translated for localized versions of the database.

In your code, you may want to access strings from this table. Because the CBO Data Service caches
this metadata, you do not need to query the string_db table to get these strings. To access the
cached copy of the strings, use the Session.FindString method:
   Session.FindString(StringID)

StringID is the ID of the string that you want to find. You can specify the ID as a numeric
constant or the name of the string. This method returns the string with the specified ID.

Note: The numeric ID is not the value specified in the string_db.id field. The numeric ID is generated
by combining the value of the string_db.id field with a base number for the subsystem. If you want to
access a string, you should use the name of the string (as specified in the string_db.name field).


The following section of code finds the heading for a login error message:
   In Java:
   String strErrorMsg = ClfySession.findString("WSP_LOGON_ERR");
   In JavaScript:
   var strErrorMsg = ClfySession.FindString("WSP_LOGON_ERR");


Updating Cached Metadata
The CBO Data Service keeps caches of metadata, including strings from the string_db table,
choice lists, locales, time zones, currency, and other information. If you add any of these types of
data to the database, the CBO Data Service must refresh its caches with the updated data before
you can use this data in your CBO application.

For example, suppose that you add a new locale to the database. In order for the locale to appear in
the list returned by the ClarifyCRM Session.GetLocaleNameList method, the CBO Data
Service t must refresh its caches with the new locale from the database.




                                                                                                         123
Getting Metadata for Your Application




                  When creating a new CBO session, the CBO Data Service checks the timestamps in a set of
                  configuration items to determine if the metadata in the database has been updated. If the
                  timestamps indicate that the metadata has been updated since the metadata was cached, the CBO
                  Data Service refreshes its caches with the latest metadata in the database.

                  When you use the ClarifyCRM client to update the metadata, the timestamps of the appropriate
                  configuration items are updated automatically. If you use the data exchange utility or some other
                  mechanism to update the metadata, you must manually update the timestamps of the
                  configuration items.

                  For more information on the metadata caches and for a list of their corresponding configuration
                  items, see the System Administration Guide.




124
C H A P T E R     3




Working with XVOs



CONTENTS
 Overview 126

 Understanding the Naming Convention for XVOs 127

 Creating an XVO 127

 Copying Data from Business Objects to XVOs 128

 Getting and Setting Field Values in an XVO 130

 Copying Data from XVOs to Business Objects 143

 Using an XVO to Add a New Row 144

 Using an XVO to Identify an Existing Row 146

 Using XVOs for Views 160

 Determining and Changing the Mode of an XVO 161

 Updating the XVO Classes 161

 Generating and Using Custom XVOs 172




                                                    125
Overview




Overview
           As explained in Understanding ClarifyCRM Value Objects (XVOs) on page 31, XVOs are lightweight,
           plain Java objects that you can use to represent a row in the ClarifyCRM database. You can use
           XVOs to copy data to and from a business object.

           The core XVO functionality is implemented in the base class com.clarify.xvobase.Xvo. Each
           table or view in the ClarifyCRM database has a corresponding default XVO class that derives from
           the Xvo base class. These XVOs are part of the com.clarify.xvo package. For example, the XVO
           for the case table is com.clarify.xvo.CaseVo.

           In situations in which you need to update the ClarifyCRM database, you can create and use XVOs
           to:
              specify the data for a new row to be added the database
              identify an existing row in the database (for example, to specify changes to that row or to
              specify a row that should be used to establish or remove a relation)

           In these situations, you can pass the XVO to a business object or a CBO component (such as an
           XBean or an EJB for an XBean), which use the data in the XVO to update the ClarifyCRM database.

           You can also call methods in business objects to retrieve rows from the business object rowset as
           XVOs. For example, you can write an XBean that uses a Generic BO for the contact table to
           retrieve contacts from the database, and you can return that data as XVOs.

           The following sections explain how to use XVOs in more detail:
              Understanding the Naming Convention for XVOs on page 127
              Creating an XVO on page 127
              Copying Data from Business Objects to XVOs on page 128
              Getting and Setting Field Values in an XVO on page 130
              Copying Data from XVOs to Business Objects on page 143
              Using an XVO to Add a New Row on page 144
              Using an XVO to Identify an Existing Row on page 146
              Using XVOs for Views on page 160
              Determining and Changing the Mode of an XVO on page 161
              Updating the XVO Classes on page 161
              Generating and Using Custom XVOs on page 172

           For information on accessing external IDs in XVOs, see the Integration Gateway Implementation
           Guide.




126
                                                                                     CHAPTER 3   Working with XVOs




Understanding the Naming Convention for XVOs
        The names of the default XVO classes and their getter and setter methods are based on the names
        of the database tables, views, fields, and indexes. The database table, view, field, and index names
        are converted in the following way to produce the resulting class and method names:
           The first letter and the letter after each underscore, hyphen, or space are changed to uppercase.
           All other letters are changed to lowercase.
           Any underscore characters and hyphens in the name are removed.
           If the resulting name is already used for another XVO class or method, the original name is used
           as is.

        For example, the getter and setter methods for the role_name field in a table are named
        getRoleName and setRoleName.

        Note: The default XVO classes follow these naming conventions. There are also other XVO classes called
        non-default XVO classes, which have their own names that are not based on the table or view name. For
        details, see Generating and Using Custom XVOs on page 172.




Creating an XVO
        If you want to pass data for a new or existing row to a business object, XBean, or EJB, you can
        create a new XVO. You can also create an XVO if you need to identify an existing row in the
        ClarifyCRM database (for example, to use as an input parameter for an XBean).

        To create the XVO, use one of the following constructors:
           To create an XVO for adding a new row to the database, use the constructor that passes a
           boolean argument, and pass true as the argument:
           // Creating an XVO for a new contact.
           ContactVo newContactVo = new ContactVo(true);
           You can then set values for fields in the row that the XVO represents, as explained in Setting
           Field Values in XVOs on page 132.

           Note: XVOs for database views do not have this constructor. You should not use XVOs for views to add
           new rows to the database.

           To create an XVO that represents an existing row, use the default constructor:
           // Creating an XVO for an existing contact.
           ContactVo existingContactVo = new ContactVo();
           You can then set criteria to identify the row that the XVO represents, as explained in Using an
           XVO to Identify an Existing Row on page 146.

        These constructors set a mode in the XVO to indicate whether the XVO represents a new or existing
        row. For details on determining the mode and changing it, see Determining and Changing the Mode of
        an XVO on page 161.




                                                                                                                 127
Copying Data from Business Objects to XVOs




Copying Data from Business Objects to XVOs
                 If you want to retrieve data from a business object as an XVO, you can call the business object
                 methods described in the following sections:
                     Copying a Row in a Business Object to an XVO on page 128
                     Copying a Rowset from a Business Object to an Array of XVOs on page 129


                 Copying a Row in a Business Object to an XVO
                 To get the data from the currently selected row as an XVO, call the BO.getVo method, and cast the
                 XVO as the specific type. For example, if the business object is the Contact BO or a Generic BO for
                 the contact table, cast the returned XVO as com.clarify.xvo.ContactVo.

                 From the returned XVO, you can retrieve the value of individual fields by calling the getter
                 methods for those fields.

                 Note: If there are no rows in the rowset of the business object, calling the BO.getVo method throws a
                 CboError.


                 For example, suppose that you are using a Generic business object for the contact table to
                 retrieve the contact first name, last name, and phone number from the database. The following
                 example gets the currently selected row as an XVO, gets the first name, last name, and phone
                 number from the XVO, and prints out this data to standard output.
                     import com.clarify.cbo.*;
                     import com.clarify.xvobase.*;
                     import com.clarify.xvo.*;
                     ...
                     BoContext boContext = null;
                     Generic genericBo = null;
                     ContactVo contactVo = null;

                     /* Set up a Generic BO for the contact table to query the database. */
                     genericBo = boContext.createGenericBO("contact");
                     genericBo.setDataFields("first_name,last_name,phone");
                     genericBo.setFilter("last_name='Smith'");
                     genericBo.query();
                     ...
                     /* Get the currently selected row as an XVO,
                          and cast the XVO to the appropriate class. */
                     contactVo = (ContactVo) genericBo.getVo();

                        /* Call getter methods for individual fields to get the field values. */
                     System.out.println(contactVo.getFirstName() + " " +
                           contactVo.getLastName() + ", Phone: " + contactVo.getPhone());
                     ...

                 You can also pass an existing XVO to the BO.getVo method to copy data from the currently
                 selected row in the business object to the existing XVO. For example:
                     /* Copy the data from the currently selected row in genericBo
                          to the existing contactVo */
                     genericBo.getVo(contactVo);



128
                                                                                 CHAPTER 3   Working with XVOs




Copying a Rowset from a Business Object to an Array of XVOs
To get the rows from a business object rowset as XVOs, call the BO.getVoAll method. This
method returns an array of Xvo objects.

If the rowset is empty (if there are no results), the method returns a zero-length array.

To extract data from the returned array of XVOs, you must first cast each XVO as the appropriate
type. For example, if the business object is the Site BO or a Generic BO for the site table, cast each
returned XVO as com.clarify.xvo.SiteVo.

Note: You cannot cast the entire array as an array of a specific type of XVO. For example, you cannot cast the
returned array of XVOs as com.clarify.xvo.SiteVo[]. If you attempt to do so, a
ClassCastException is thrown. You must individually cast each XVO in the returned array as its
specific type.


Then, you can call the getter methods in each XVO to retrieve the value of each field. For more
information about calling getter methods to get field values from an XVO, see Getting Field Values
from an XVO on page 131.

For example, suppose that you are using a Generic business object for the contact table to
retrieve the contact first name, last name, and phone number from the database. The following
example gets the search results as an array of XVOs, gets the first name, last name, and phone
number from each XVO, and prints out this data to standard output.
   import com.clarify.cbo.*;
   import com.clarify.xvobase.*;
   import com.clarify.xvo.*;
   ...
   BoContext boContext = null;
   Generic genericBo = null;
   ContactVo contactVo = null;
   Xvo[] arrayVo = null;
   ...
   /* Set up a Generic BO for the contact table to query the database. */
   genericBo = boContext.createGenericBO("contact");
   genericBo.setDataFields("first_name,last_name,phone");
   genericBo.setFilter("last_name='Smith'");
   genericBo.query();
   ...
   /* Get the data from the rowset as an array of XVOs. */
   arrayVo = genericBo.getVoAll();

   for (int n = 0; n < arrayVo.length; n++) {
      /* Cast each XVO as the appropriate subclass */
      contactVo = (ContactVo)arrayVo[n];

       /* Call getter methods for individual fields to get the field values. */
       System.out.println(contactVo.getFirstName() + " " +
          contactVo.getLastName() + ", Phone: " + contactVo.getPhone());
   }
   ...




                                                                                                           129
Getting and Setting Field Values in an XVO




Getting and Setting Field Values in an XVO
                  Each XVO class implements getter and setter methods for each field in the database table row
                  represented by the XVO. XVOs also provide getter and setter methods for choice fields for that
                  table. For more information on choice fields, see Working with ClarifyCRM Choice and User Choice
                  Lists on page 108.

                  The following sections explain how to access field values in an XVO:
                      Understanding the Java Types Used for Database Data Types on page 130
                      Getting Field Values from an XVO on page 131
                      Setting Field Values in XVOs on page 132
                      Getting and Setting Currency Values on page 133
                      Getting and Setting Values for Choice Fields on page 134
                      Getting and Setting Flexible Attributes in an XVO on page 138
                      Tracking Unsaved Changes in XVO Fields on page 142

                  For details on accessing external IDs in XVOs, see the Integration Gateway Implementation Guide.


                  Understanding the Java Types Used for Database Data Types
                  In an XVO, the getter and setter methods pass in and return Java types that correspond to the type
                  of data stored in the actual database field. Table 9 and Table 10 list the Java types used for the
                  parameters and return values in getter and setter methods.

                  Table 9     Fields in database schema and corresponding Java types used in accessor methods
                   Database Type in Data Dictionary Corresponding Java Type
                   tinyint (1 byte)                    byte
                   smallint (2 bytes)                  short
                   int (4 bytes)                       int
                   real                                float
                   Floating point 8-byte data          double
                   Currency data                       ClfyCurrency
                   decimal                             BigDecimal
                   varchar, text                       String
                   datetime                            Date (time in UTC)


                  Table 10 Additional field types and corresponding Java types used in accessor methods
                   Field Type in Table                         Corresponding Java Type
                   Object ID or foreign key (see Getting the   com.clarify.xvobase.ClfyObjid (except in XVOs for
                   Object ID of a Row on page 62)              views, which represent object IDs as Java String objects)
                   Choice field (see Getting and Setting Values com.clarify.xvobase.ClfyChoiceData
                   for Choice Fields on page 134)
                   External ID (for details, see the Integration com.clarify.xvobase.ClfyExternId
                   Gateway Implementation Guide)



130
                                                                                 CHAPTER 3   Working with XVOs




Getting Field Values from an XVO
To get the value of a field from a row, call the getter method for the field. The following code gets
the value of the name field in the site table by calling the getName method in the SiteVo object.
      /* Call getter methods for individual fields to get the field values. */
   System.out.println(siteVo.getName());

The getter methods in an XVO return the field value as the appropriate class of object (for example,
java.util.Calendar for date-time values, java.math.BigDecimal for decimal values, and
com.clarify.xvobase.ClfyCurrency for currency values).

Note: If the XVO represents a view, the object ID accessor methods use strings, not ClfyObjid objects.


For details on getting data from the following types of fields, see the sections below:
   To get the value of the objid field, see Getting the Object ID from an XVO for Table on page 132.
   To get the value of a currency field, see Getting and Setting Currency Values on page 133.
   To get the values of a choice field, see Getting and Setting Values for Choice Fields on page 134.
   To get flexible attributes, see Getting and Setting Flexible Attributes in an XVO on page 138.

Note: If you need to present a nonstring value to an end user, use the toString methods in the CBO
Session object (such as toString, toCurrencyString, toDateString, toNumberString) to
get the string representation of the value. This method takes the end user’s locale and time zone into account
when converting values to strings. See Working with Field Values and Data Types on page 92.


If you query the database and call the getter method for a field has not been retrieved or set to a
value, the method returns a default value or null. To distinguish between a field not retrieved from
the database and a field that has no value in the database, call the isEmpty method of your XVO:
   If the field was not retrieved from the database, the isEmpty method returns true.
   If the field was retrieved from database, the isEmpty method returns false. The isEmpty
   method also returns false if you set the field to a value.

For example, the following statement checks if the e_mail field is set in ContactVo before calling
the getEMail method to retrieve the field value:
   /* Check to see if the field has a value. */
   if (!contactVo.isEmpty("e_mail")) {
        System.out.println(contactVo.getEMail());
   }

When calling the isEmpty method, you can either pass in the actual field name or the part of the
getter method name that specifies the field (for example, FirstName if the getter method is
getFirstName, or CaseTypeLvl1 if the getter method is getCaseTypeLvl1ChoiceData).

The following example is similar to the example above. This example uses the name EMail (from
the getter method named getEMail), which is equivalent to using the actual field name e_mail:
   /* You can also use the name from the getter method as the field name. */
   if (!contactVo.isEmpty("EMail")) {
        System.out.println(contactVo.getEMail());
   }



                                                                                                           131
Getting and Setting Field Values in an XVO




                  Setting Field Values in XVOs
                  XVOs provide setter methods for writable fields. The setter methods are strongly typed, so that
                  errors in type can be caught when compiling your code.

                  Note: While XVOs for views provide setter methods for fields, you cannot use these setter methods to change
                  field values. If you need to change a field value, use an XVO for a table.


                  The following example calls the setName method of the SiteVo object to set the name of a site:
                      String strName;
                      ...
                      // Create a SiteVo for a new site.
                      (SiteVo) siteVo = new SiteVo(true);
                      // Set the name field for a new site.
                      siteVo.setName(strName);
                      ...

                  For details on setting the values of the following types of fields, see the sections below:
                      For details on setting values in currency fields, see Getting and Setting Currency Values on
                      page 133.
                      For details on setting values in choice fields, see Getting and Setting Values for Choice Fields on
                      page 134.
                      For details on setting flexible attributes, see Getting and Setting Flexible Attributes in an XVO on
                      page 138.

                  When you set the value of a field in an XVO, the XVO keeps track of the fact that the field value has
                  been modified. You can call methods in the XVO to determine if a given field contains
                  uncommitted changes, as explained in Tracking Unsaved Changes in XVO Fields on page 142.


                  Getting the Object ID from an XVO for Table
                  If you used an XVO to get data from a business object that represents a table, you can get the object
                  ID from the XVO by calling one of the following methods:
                      The getObjid method returns the object ID as a string.
                      The getClfyObjid method returns a ClfyObjid object, which contains the object ID as well
                      as the name of the table or view that the XVO represents.

                  If the XVO is cast as the com.clarify.xvobase.Xvo base class, you must first cast the XVO as
                  its derived type (for example, CaseVo, SiteVo, ContactVo, etc.).

                  If you are calling the getClfyObjid method, you can get the object ID by calling the getObjid
                  method of the returned ClfyObjid object. If you need to get the table or view name, you can call
                  the getTableName method of this object.

                  The following example gets and prints the object ID from a ContactVo object:
                      import com.clarify.xvobase.*;
                      import com.clarify.xvo.*;
                      ...
                      /* Get an XVO and cast it to its specific type. */
                      ContactVo contactVo = (ContactVo) boContact.getVo();


132
                                                                           CHAPTER 3   Working with XVOs




   /* Get and print the object ID */
   System.out.println("Object ID: " + contactVo.getObjid();

   /* You can also use the following technique to get the object ID and table/view
   name. */

   /* Get the ClfyObjid object for the XVO. */
   ClfyObjid myContactObjid = contactVo.getClfyObjid();
   /* Print the object ID of the row in the XVO and the table/view name */
   if (myContactObjid != null) {
      System.out.println("Object ID: " + myContactObjid.getObjid());
      System.out.println("Table Name: " + myContactObjid.getTableName());
   }
   ...


Getting and Setting Currency Values
In XVOs, fields that hold currency values have accessor methods that pass in and return objects of
the com.clarify.xvobase.ClfyCurrency class. Objects of this class represent the decimal
value of a currency field.

To set the value of a currency field in an XVO, do the following:
1. Create a new ClfyCurrency object and pass the constructor a BigDecimal object
   representing the decimal value and a 3-letter ISO 4217 currency code representing the currency.
   For the decimal value of the currency, you can also pass in the string representation of the value,
   a double, or a BigInteger object (optionally specifying the scale as an argument), and the
   constructor will convert the specified value to a decimal value.
   This string representation must have a period as the decimal separator and must not include
   any grouping characters, currency symbol, or any other extraneous characters.
   Alternately, you can use the default constructor and call the setDecValue and
   setCurrencyCode methods of the ClfyCurrency object to set the decimal value and
   currency.
2. Pass the ClfyCurrency object to the setter method for the field.
   For example:
   import com.clarify.xvobase.*;
   import com.clarify.xvo.*;
   import java.math.*;
   ...
   // Create a ClfyCurrency object for the value to set.
   String strCurrencyValue = "100.00";
   String strCurrencyCode = "USD";
   ClfyCurrency currencyValue =
        new ClfyCurrency(strCurrencyValue, strCurrencyCode);
   // Call the setter method of the XVO and pass in the ClfyCurrency object
   myVo.setMyCurrencyField(currencyValue);
   ...




                                                                                                    133
Getting and Setting Field Values in an XVO




                  To get the value of a currency field from an XVO, do the following:
                   1. Call the getter method for the currency field in the XVO to get the value as a ClfyCurrency
                      object.
                   2. Call the getDecValue method of the ClfyCurrency object to get the value as a BigDecimal
                      object.
                      For example:
                      import com.clarify.xvobase.*;
                      import com.clarify.xvo.*;
                      import java.math.*;
                      ...
                      // Get the value of the field from the XVO as a ClfyCurrency object.
                      ClfyCurrency myCurrencyValue = myVo.getMyCurrencyField();
                      // Get the decimal value.
                      BigDecimal myDecimalValue = myCurrencyValue.getDecValue();
                      ...


                  Getting and Setting Values for Choice Fields
                  As explained in Working with ClarifyCRM Choice and User Choice Lists on page 108, a business object
                  can contain one or more choice fields. A choice field represents a selected item from a choice list.

                  Note: For a list of choice fields and their default choice lists, see the chapter on the ChoiceList object in
                  the Core Business Objects Reference Guide.


                  XVOs are designed to encapsulate choice fields. The following sections explain how to get and set
                  choice fields in XVOs.
                      For information on how XVOs provide access to choice field data, see Understanding How Choice
                      Fields Are Represented in XVOs on page 134.
                      If you need to access the displayed title of the currently selected choice, see Getting the Displayed
                      Title of Currently Selected Choice on page 135.
                      If you need to get additional data on the currently selected choice (such as the rank or state of
                      the choice, or the reference IDs of the choice and choice list), see Getting Details on the Currently
                      Selected Choice on page 135.
                      If you need to set a choice field, see Setting a Choice Field on page 137.

                  Understanding How Choice Fields Are Represented in XVOs
                  In XVOs, choices are represented by the ClfyChoiceData class. The ClfyChoiceData class
                  provides getter and setter methods for accessing the information about the choice, such as the
                  displayable title, reference ID, object, ID, and rank.

                  For each choice field in a table, the XVO class for that table provides a getter and setter method that
                  passes in and returns a ClfyChoiceData object:
                      // Methods for getting and setting a choice as a ClfyChoiceData object
                      public ClfyChoiceData getXxxChoiceData()
                      public void setXxxChoiceData(ClfyChoiceData value)




134
                                                                                 CHAPTER 3   Working with XVOs




XVOs also provide a getter method to retrieve the title of the currently selected choice:
   // Method for getting the displayed title of the choice
   public String getXxx()

Note: While XVOs also provide a setter method for passing in the title of the selected choice, this method is
provided only for backward compatibility. If you need to set a new choice, you should always use the setter
method that passes in a ClfyChoiceData object.


For example, the CaseVo class has the following methods for getting and setting the priority
and severity choice fields:
   // Method for getting the displayed title of the current priority
   public String getPriority()

   // Methods for getting and setting the priority as a ClfyChoiceData object
   public ClfyChoiceData getPriorityChoiceData()
   public void setPriorityChoiceData(ClfyChoiceData value)

   // Method for getting the displayed title of the current severity
   public String getSeverity()

   // Methods for getting and setting the severity as a ClfyChoiceData object
   public ClfyChoiceData getSeverityChoiceData()
   public void setSeverityChoiceData(ClfyChoiceData value)

Getting the Displayed Title of Currently Selected Choice
If you want to get the displayed title of the currently selected choice, call the getter method for the
choice field that returns a string value. The following example gets the selected choice for the case
priority:
   /* Get and print the case priority. */
   System.out.println("Case Priority: " + caseVo.getPriority());

If the relation represented by the choice field is not set, the getter method returns null.

Getting Details on the Currently Selected Choice
If you want to get additional details on the currently selected choice, call the getter method for the
choice field that returns a ClfyChoiceData object. From this object, you can get the information
listed in Table 11.

Table 11 Accessing choice information from the ClfyChoiceData
To get the following information..                               Call the following ClfyChoiceData method...
Reference ID for the currently selected choice                   getRefId
Object ID of the currently selected choice                       getId
Displayable title of the currently selected choice               getTitle
Description of the currently selected choice                     getDescription
Rank of the currently selected choice                            getRank




                                                                                                           135
Getting and Setting Field Values in an XVO




                  Table 11 Accessing choice information from the ClfyChoiceData (Continued)
                   To get the following information..                                 Call the following ClfyChoiceData method...
                   State of the currently selected choice                             getState
                      For choices representing states/provinces in a country,
                      countries, and time zones, the state 1 indicates the default
                      choice. All other choices have the state 0.
                      For choices representing channels in a medium, the state 1
                      represents an active choice, and the state 0 represents an
                      inactive choice.
                      For other types of choices, the state 0 represent an active
                      choice, 1 represents an inactive choice, and 2 represents the
                      default choice.
                   Determine if the choice is currently active or inactive            isActive
                   Determine if the choice is the default choice                      isDefault
                   Reference ID for the choice list containing the currently          getListRefId
                   selected choice
                   Name of the choice list containing the currently selected choice getListName



                  Note: For choice lists that have no names (for example, the list of all countries and the list of all time zones),
                  the ListRefId and ListName properties are set to empty strings.


                  For a complete list of the properties available in this object, see the Core Business Objects Reference
                  Guide.

                  The following example gets the selected choice for the state_prov choice field and the name of
                  the choice list containing that choice:
                      /* Get the properties of the selected choice for state/province. */
                      ClfyChoiceData stateProvChoice = myAddressVo.getStateProvChoiceData();
                      if (stateProvChoice != null) {

                              // Get the title of the select choice.
                              strChoiceName = stateProvChoice.getTitle();

                              // Get the name of the choice list containing that choice.
                              strChoiceListName = stateProvChoice.getChoiceListName());
                      }

                  For an existing row in the database, if the relation represented by the choice field is not set, the
                  ClfyChoiceData object specifies default values for its properties. The string properties (Title,
                  Description, RefId, ListName, and ListRefId) are set to empty strings. Rank is set to 0,
                  State is set to 1, and isActive and isDefault return false.

                  For an XVO returned from a new row in a business object that has not been committed to the
                  database, the ClfyChoiceData object specifies the default choice and the default choice list.




136
                                                                              CHAPTER 3   Working with XVOs




Setting a Choice Field
To set a choice field in an XVO, do the following:
1. Create a new ClfyChoiceData object to represent the new choice. (See the rest of this section
   for an explanation of the arguments that you need to pass to the constructor.)
2. Call the setter method for the choice field, passing in the ClfyChoiceData object.

Note: When you set a choice field, validation is performed only when you copy the XVO to a business object
(for example, when you call the BO.addVo method). If the specified choice does not exist, the BO.addVo
method throws a CboError.


If the choice field is only associated with a single choice list, or if you want to select a choice from
the default choice list, specify the reference ID of the choice that you want to assign. You can do this
in one of the following ways:
   Use the constructor of the ClfyChoiceData object that allows you to pass in the reference ID
   of the choice. For example:
       ...
       // Create the ClfyChoiceData object for the choice "Incident" for case type
       ClfyChoiceData myChoice = new ClfyChoiceData("Incident");
       // Set the new choice in the XVO
       myCaseVo.setCaseTypeChoiceData(myChoice);
       ...
   Use the default constructor and then call the setRefId method to set the reference ID of the
   choice. For example:
       ...
       // Create the ClfyChoiceData object for the choice "Incident" for case type
       ClfyChoiceData myChoice = new ClfyChoiceData();
       myChoice.setRefId("Incident");
       // Set the new choice in the XVO
       myCaseVo.setCaseTypeChoiceData(myChoice);
       ...

If you are setting the choice field to a choice that is not in the default choice list, you must specify
the reference IDs for both the selected choice and the choice list containing that choice. You might
need to use this approach in the following situations:
   In an AddressVo or DemandHdrVo object, you might want to set the state_prov choice field
   to a state or province that is not in the default country. To identify this choice, you must specify
   the reference IDs for both the state/province and the country.
   In an XVO for a workflow object (such as CaseVo and OpportunityVo), you might want to set
   the status choice field to a status that is not in the default condition. To identify this choice,
   you must specify the reference IDs for both the status and the condition.
   In a CommunicationVo or ChannelInstVo object, you might want to set the channel choice
   field to a channel that is not in the default medium. To identify this choice, you must specify the
   reference IDs for both the channel and the medium.




                                                                                                       137
Getting and Setting Field Values in an XVO




                  To do this, you create a new ClfyChoiceData object in one of the following ways:
                      Use the constructor of the ClfyChoiceData object that allows you to pass in the reference IDs
                      of the choice and the choice list containing that choice. For example:
                          ...
                          // Create the ClfyChoiceData object for the choice "BC" in the choice list
                          // "Canada" (NOTE: This assumes that Canada is a country in your database.)
                          ClfyChoiceData myChoice = new ClfyChoiceData("BC", "Canada");
                          // Set the new choice in the XVO
                          myAddressVo.setStateProvChoiceData(myChoice);
                          ...
                      Use the default constructor and then call the setRefId and setListRefId methods to set the
                      reference IDs of the choice and the choice list containing that choice. For example:
                          ...
                          // Create the ClfyChoiceData object for the choice "BC" in the choice list
                          // "Canada" (NOTE: This assumes that Canada is a country in your database.)
                          ClfyChoiceData myChoice = new ClfyChoiceData();
                          myChoice.setListRefId("Canada");
                          myChoice.setRefId("BC");
                          // Set the new choice in the XVO
                          myAddressVo.setStateProvChoiceData(myChoice);
                          ...

                  You do not need to set any other properties in the newly created ClfyChoiceData object. You just
                  need to specify the reference IDs of the choice and choice list.

                  Note: For backwards compatibility, you can still use the displayable title of the choice and the displayable
                  name of the choice list, instead of the reference IDs (for example, in the constructor or when calling the
                  setChoiceName and setChoiceListName methods). If you are writing new code, you should use the
                  ClfyChoiceData object to set the reference IDs instead of the displayable title and name.



                  Getting and Setting Flexible Attributes in an XVO
                  Flexible attributes in XVOs are represented by the com.clarify.xvobase.ClfyFlexAttr
                  class. If an XVO represents a table that supports flexible attributes, that XVO defines methods for
                  getting and setting the flexible attributes in the XVO.

                  For example, because the case table supports flexible attributes, the CaseVo class has accessor
                  methods for flexible attributes.

                  The following sections explain how to get and set flexible attributes in XVOs:
                      Copying Flexible Attributes from a Business Object to an XVO on page 139
                      Getting Flexible Attributes from an XVO on page 139
                      Getting the Value of a Flexible Attribute on page 139
                      Setting Flexible Attributes in an XVO on page 141

                  For more information on flexible attributes, see the Flexible Attributes Guide.




138
                                                                               CHAPTER 3   Working with XVOs




Copying Flexible Attributes from a Business Object to an XVO
To get an XVO from a business object and copy any flexible attributes to the XVO, you can call the
BO.getVo or BO.getVoAll method, and pass true as an argument to these methods. For
example:
   ...
   // Set up a business object to query for cases
   Generic boGenCase = boContext.createGenericBO("case");
   boGenCase.setDataFields("*");
   boGenCase.setFilter(strFilter); // Set some search criteria (not shown here)
   boGenCase.query();

   // Get an XVO for the current row and copy any flexible attributes to the XVO
   CaseVo voCase = (CaseVo) boGenCase.getVo(true);

   // Get an array of XVOs for all rows and copy any flexible attributes to the XVOs
   Xvo[] aryVoCase = boGenCase.getVoAll(true);
   ...

Note: If you are using the ClfyBulk EJB to query for data, you can also use the EJB to retrieve any flexible
attributes for each row found. For details, see the Integration Gateway Implementation Guide.


Getting Flexible Attributes from an XVO
To get flexible attributes from an XVO, you can call one of the following methods:
   To get a specific flexible attribute from an XVO, call the getFlexAttribute method of the
   XVO, and pass in the name of the flexible attribute that you want to retrieve.
   The flexible attribute is returned in a ClfyFlexAttr object. If the XVO does not contain a
   flexible attribute with that specified name, the method returns null.
   To get all flexible attributes from an XVO, call the getFlexAttributes method of the XVO.
   This method returns the flexible attributes as an array of ClfyFlexAttr objects. If the XVO
   contains no flexible attributes, the method returns null.

The following example gets the flexible attribute named my_flex_attr from an XVO. The
example also gets all flexible attributes from the XVO.
   ...
   // Get a row and its flexible attributes in a CaseVo object
   CaseVo voCase = (CaseVo) boGenCase.getVo(true);

   // Get the flexible attribute named my_flex_attr
   ClfyFlexAttr myFlexAttr = voCase.getFlexAttribute("my_flex_attr");

   // Get all flexible attributes for the row in the XVO
   ClfyFlexAttr[] aryMyFlexAttrs = voCase.getFlexAttributes();
   ...

Getting the Value of a Flexible Attribute
From a ClfyFlexAttr object, you can get the value of the flexible attribute by calling one of the
methods listed in Table 12.




                                                                                                         139
Getting and Setting Field Values in an XVO




                  Table 12 Methods for getting the value of a flexible attribute from a ClfyFlexAttr object
                   Data Type of the          Corresponding Java Class/    Method to Get the Value of the Flexible Attribute
                   Flexible Attribute        Data Type
                   Long                      int                          intValue
                   String                    java.lang.String             stringValue
                   DateTime                  java.util.Date               dateValue
                   Decimal                   java.math.BigDecimal         decimalValue


                  If the flexible attribute in the ClfyFlexAttr object cannot be converted to the type returned by
                  the method, the method throws an exception.

                  If you do not already know the data type of the flexible attribute, you can call the
                  getDataClassName method of the ClfyFlexAttr object to determine the data type. This
                  method returns one of the following strings:
                      "int"
                      "java.lang.String"
                      "java.util.Date"
                      "java.math.BigDecimal"

                  The following example gets the flexible attributes from a CaseVo object.
                      // Query the database for cases
                      boGenCase.query();

                      // Get the XVO for the current row and copy the flexible attributes into the XVO
                      CaseVo voCase = (CaseVo) boGenCase.getVo(true);
                      // Get all of the flexible attributes in this XVO
                      ClfyFlexAttr[] aryFlexAttr = voCase.getFlexAttributes();
                      // Check if the XVO had no flexible attributes
                      if (aryFlexAttr == null) {
                         System.out.println("No flex attributes found.");
                      } else {
                         System.out.println(" " + aryFlexAttr.length + " attributes found.");
                         for (int i = 0; i < aryFlexAttr.length; i++) {
                            ClfyFlexAttr myFlexAttr = aryFlexAttr[i];
                            // Check the type of the flexible attribute, and call the appropriate
                            // method to get the value of the flexible attribute.
                            String strClassName = myFlexAttr.getDataClassName();
                              if (strClassName.equals("int")) {
                                int nFlexAttrValue = myFlexAttr.intValue();
                                ...
                            } else if (strClassName.equals("java.lang.String")) {
                                String strFlexAttrValue = myFlexAttr.stringValue();
                                ...
                            } else if (strClassName.equals("java.util.Date")) {
                                Date dateVal = myFlexAttr.dateValue();
                                ...
                            } else if (strClassName.equals("java.math.BigDecimal")) {
                                java.math.BigDecimal decimalVal = myFlexAttr.decimalValue();
                                ...
                            }


140
                                                                             CHAPTER 3   Working with XVOs




         ...
      }
      ...
   }
   ...

Setting Flexible Attributes in an XVO
To set flexible attributes in an XVO, use one of the following techniques:
   To set a specific flexible attribute in an XVO, call the setFlexAttribute method of the XVO,
   and pass in the name of the flexible attribute and the value that you want to assign.
   To set a group of flexible attributes in an XVO:
   a. Create a ClfyFlexAttr object for each flexible attribute.
   b. Create an array of these ClfyFlexAttr objects.
   c. Call the setFlexAttributes method of the XVO, passing in this array of ClfyFlexAttr
      objects.

When setting a flexible attribute, make sure to use a value of the same type that the flexible
attribute uses. For a listing of the flexible attribute types and their corresponding Java types, see
Getting the Value of a Flexible Attribute on page 139.

The setFlexAttribute and setFlexAttributes methods do not validate that the specified
value complies with the definition of the flexible attribute in the database.

Validation is only performed when you copy the XVO to a business object (for example, when you
call the BO.addVo method). If the specified value cannot be converted to the type of the flexible
attribute, the BO.addVo method throws a CboError.

The following example sets a flexible attribute named my_flex_attr in a CaseVo object. The
example also sets a group of flexible attributes in the same object.
   CaseVo voCase = new CaseVo();
   // Values to assign to flexible attributes
   String strNewValue = "New Value";
   java.math.BigDecimal decValue = new java.math.BigDecimal("100.53");
   ...
   // Set the flexible attribute my_flex_attr in the CaseVo object.
   voCase.setFlexAttribute("my_flex_attr", strNewValue);
   ...
   // Set a group of flexible attributes.
   ClfyFlexAttr myStringFlexAttr =
      new ClfyFlexAttr("my_string_attribute", strNewValue);
   ClfyFlexAttr myLongFlexAttr = new ClfyFlexAttr("my_long_attribute", 10101);
   ClfyFlexAttr myDecimalFlexAttr = new ClfyFlexAttr("my_decimal_attribute",
      decValue);
   ClfyFlexAttr myDateTimeFlexAttr = new ClfyFlexAttr("my_datetime_attribute",
      new java.util.Date());
   ClfyFlexAttr[] aryMyFlexAttrs = {myTestFlexAttr, myStringFlexAttr,
      myLongFlexAttr, myDecimalFlexAttr, myDateTimeFlexAttr};
   voCaseMod.setFlexAttributes(aryMyFlexAttrs);




                                                                                                      141
Getting and Setting Field Values in an XVO




                  Tracking Unsaved Changes in XVO Fields
                  XVO classes are designed to track changes made to their fields. When you call a setter method to
                  assign or change the value of a field, the XVO internally marks the field as “dirty”.

                  In addition, if you use the BO.getVo or BO.getVoAll method to copy data to an XVO, any fields
                  with uncommitted changes in the business object are marked as “dirty” in the XVO.

                  To determine if a field has unsaved changes, use the isFieldDirty method of the XVO, passing
                  in the name of the field.

                  As is the case with the isEmpty method (see Getting Field Values from an XVO on page 131), you
                  can either pass in the actual field name or the part of the setter method name that specifies the field
                  (for example, FirstName if the setter method is setFirstName, or CaseTypeLvl1 if the setter
                  method is setCaseTypeLvl1ChoiceData).

                  The following example checks a ContactVo object to determine if the first_name field contains
                  an unsaved change:
                      ...
                      // Get a ContactVo object from the current row in a Generic BO for contact
                      ContactVo voContact = (ContactVo) boGenContact.getVo();
                      ...
                      // Check if the first_name field has been set or contains uncommitted changes
                      if (voContact.isFieldDirty("first_name")) { // You can also use "FirstName"
                         ... // The field has unsaved changes
                      }
                      ...

                  As mentioned previously, if you get the XVO from a business object and the field in the business
                  object contains unsaved changes, the corresponding field in the XVO is also marked as “dirty”:
                      ...
                      // A change is made to a field in the business object
                      boGenContact.setValue("last_name", "bar");
                      // Get a ContactVo object from the current row with the unsaved change
                      ContactVo voContact = (ContactVo) boGenContact.getVo();
                      // Check for unsaved changes
                      if (voContact.isFieldDirty("LastName")) { // You can also use "last_name"
                         ... // isFieldDirty returns true because the field in the BO
                         ... // had uncommitted changes when the field was copied to the ContactVo.
                      }
                      ...

                  To mark all fields in an XVO as “not dirty”, call the clearDirtyFlags method. For example:
                      // Mark all fields as not dirty
                      voContact.clearDirtyFlags();




142
                                                                                          CHAPTER 3   Working with XVOs




Copying Data from XVOs to Business Objects
        If an XVO contains data that you want to commit to the ClarifyCRM database, you can call the
        BO.addVo method to copy the data from the XVO to a business object.

        Note: If you are updating data in a table that does not have a specific type of business object (such as Case
        BO, Site BO, or Contact BO), you can pass the XVOs to the BoContext.updateAll method to commit
        your changes. For details, see Performing Bulk Updates with XVOs on page 321.


        The XVO and the business object must represent the same database table or view.

        When calling this method, pass in the XVO as an argument. For example:
           ...
           // Copy data from the XVO voSite to the business object boSite
           boSite.addVo(voSite);
           ...

        Note: If the XVO represents an existing row, you must set up the XVO with the criteria for finding the
        existing row before calling the BO.addVo method. For details on setting up this criteria, see Using an XVO
        to Identify an Existing Row on page 146.


        If the XVO represents a row that you want to use to add, replace, or remove a relation, you can do
        any of the following:
           If you are establishing a relation by using a child business object (or a contained business object)
           for the same table as the XVO, you can call the BO.addVo method in the child business object to
           copy the object ID from the XVO to the business object. For example:
               ...
               // Using a child BO to establish a relation
               boChild.addVo(voRowToRelate);
               ...
           This is equivalent to calling the BO.replaceById method in the child business object to add or
           replace a relation. (The technique that uses the BO.replaceById method is explained in
           Adding or Replacing a Child by Object ID on page 202.)
           If there is a currently selected row in the child business object, the BO.addVo method replaces
           that row. If there is no currently selected row in the child business object, the BO.addVo method
           adds a new row.
           You can call the Xvo.getObjid method to get the object ID from the XVO, and then call the
           BO.relateById method to establish the relation or the BO.unrelateById method to
           remove the relation.
           If you are working with a part of the data model that a specific type of business object (such as
           Case BO, Site BO, or Contact BO), you can use the BoContext.updateAll method to set up
           or remove the relation. For details, see Performing Bulk Updates with XVOs on page 321




                                                                                                                    143
Using an XVO to Add a New Row




Using an XVO to Add a New Row
                To use an XVO to add new rows to a database table, do the following:
                 1. Use the XVO constructor with the boolean parameter to create a new XVO object for the new
                    row. Pass true as the argument to the constructor. For example:
                    // Creating a new ContactVo to add a new row to the database
                    ContactVo newContactVo = new ContactVo(true);
                    true specifies that you want to use this XVO to add a new row. If you instead pass false to
                    the constructor or use the default constructor, the newly created XVO represents an existing row
                    in the database. (See Creating an XVO on page 127 for details.)
                 2. Call setter methods in the XVO to set values for the fields in the row that you want to add. For
                    details, see Getting and Setting Field Values in an XVO on page 130.
                 3. Call the BO.addVo method and pass in the newly created XVO. This method adds a new row to
                    the rowset in the business object and copies the data from the XVO to the new row.
                 4. If you are using a Generic business object for a table uses a numbering scheme for IDs, call the
                    Field.setNumSchemeName method of the ID field to the name of the numbering scheme. For
                    details, see Setting the Numbering Scheme on page 75.
                 5. Set up any relations needed, as explained in Chapter 4, Working with Database Relations.
                 6. Call the BoContext.updateAll method to commit the row to the database. For details, see
                    Committing Changes to the Database on page 83.

                The following is an example of an EJB client that creates and sets up an XVO to add a new row to
                the contact table. The EJB client passes the XVO to an EJB method for creating contacts.
                    import com.clarify.cbo.*;
                    import com.clarify.xvobase.*;
                    import com.clarify.xvo.*;
                    ...
                    // Create a Contact VO and specify values for the fields
                    // in the contact table.
                    ContactVo contactVo = new ContactVo(true);
                    contactVo.setFirstName(strFirstName);
                    contactVo.setLastName(strLastName);
                    contactVo.setPhone(strPhone);
                    ...
                    // Set additional fields
                    ...
                    // Create a ContactRole VO and specify values for the fields
                    // in the contact_role table.
                    ContactRoleVo contactRoleVo = new ContactRoleVo(true);
                    contactRoleVo.setPrimarySite(nIsPrimarySite);
                    contactRoleVo.setRoleName(strContactRole);
                    // Note that contact_role is the name of a choice field, which
                    // corresponds to the contact_role2gbst_elm relation. Passing in
                    // the displayed value of the selected choice sets the relation to
                    // the corresponding gbst_elm row when the data is saved.
                    contactRoleVo.setContactRole(strContactRole);
                    // Associate the contact with an existing site identified by the
                    // site ID (the variable strSiteId). Use the existing database index
                    // on the site_id field to find the site.
                    SiteLookupByIdIndex siteLookup = new SiteLookupByIdIndex(strSiteId);
                    SiteVo siteVo = new SiteVo();


144
                                                                     CHAPTER 3   Working with XVOs




   siteVo.setLookupByIdIndex(siteLookup);
   // Call the EJB method for creating the contact.
   ClfyObjid newContactObjid = myEJB.createContact(strSessionId,
      contactVo, contactRoleVo, siteVo, ... /* additional parameters */ );
   ...

The following is the preExecute method of the XBean used by the EJB. The preExecute method
resolves the XVOs passed in previously through setter methods and copies data from the XVOs to
a Contact business object. The executeImpl method then uses the business object to update the
database with the new contact.
   import com.clarify.cbo.*;
   import com.clarify.xvobase.*;
   import com.clarify.xvo.*;
   ...
   protected void preExecute(BoContext boContext) {
      ...
      try {
         // Find the site, and fill the XVO with the object ID of the site.
         boContext.resolveLookups(new Xvo[] {m_newContactVo, m_newContactRoleVo,
            m_existingSiteVo});
         if ((m_existingSiteVo == null) || (m_existingSiteVo.getObjid() == null)) {
            ...
            // Handle the situation in which m_existingSiteVo is null or no lookup
            // criteria was specified in m_existingSiteVo.
            ...
         }
      } catch(CboError e) {
         ...
         // Handle the situation in which the lookup criteria for m_existingSiteVo
         // does not match any existing site or matches multiple sites.
         ...
      }
      ...
      // Set up additional XVOs to find rows to relate to
      ...
      // Create a Contact BO to add the new contact.
      Contact contactBo =
         (Contact) boContext.createBO("com.clarify.cbo.Contact");
      // Add a new row to the rowset in the BOs, and copy the data from
      // the corresponding VO to the new row. The contained ContactRole
      // and Site BOs are used to set relations to the contact. For details
      // on relations, see Chapter 4, Working with Database Relations.
      contactBo.addVo(m_newContactVo);
      contactBo.getContactRole().addVo(m_newContactRoleVo);
      contactBo.getSite().addVo(m_existingSiteVo);
      ...
      // Set up additional contained BOs and additional relations to other tables.
      ...
   }

   protected void executeImpl(BoContext boContext) {
      ...
      // Save the new row to the database.
      boContext.updateAll();
      ...
   }



                                                                                              145
Using an XVO to Identify an Existing Row




Using an XVO to Identify an Existing Row
                  An application that does not use CBO (such as an EJB client) may need to pass updated data for a
                  row or an identifier for a row (for example, in order to establish a relation) to a CBO application.

                  To represent the row, the application should use an XVO that contains search criteria that identifies
                  the row. After the application passes the XVO to a CBO application, the CBO application calls the
                  BoContext.resolveLookups method to find the row and copy the object ID into the XVO.

                  Note: If the search criteria identifies zero rows or more than one row, the BoContext.resolveLookups
                  method throws a CboError exception (unless no search criteria was specified).


                  For example, suppose that you write an XBean (with an EJB layer) for adding a new contact to the
                  database. One of the input values for your XBean is a SiteVo object representing an existing site
                  that the contact plays a role at. In this situation, the EJB client (which does not have access to CBO
                  or the ClarifyCRM database) sets up a SiteVo object to identify the site to use (for example, by
                  specifying the value of the site_id field as the search criteria). The EJB client passes this SiteVo
                  object to the EJB, which in turn passes the object to a setter method of the XBean. The XBean can
                  pass this SiteVo object to the BoContext.resolveLookups method to find the row in the
                  database. (See Finding the Row for an XVO on page 158.)

                  Note: If you are passing the XVO to the ClfyBulk EJB, the EJB internally finds the row for the XVO in
                  the database. For details on the ClfyBulk EJB, see the Integration Gateway Implementation Guide.


                  This technique avoids an extra round-trip between the EJB client and the EJB. The EJB client does
                  not need to use the EJB to query and retrieve the site as an XVO before passing the XVO to the EJB
                  again for use in updating a row or establishing a relation.

                  The BoContext.resolveLookups method queries the database using the criteria specified in
                  each XVO and copies the object ID of the resulting row into the corresponding XVO. For example,
                  in the case of the SiteVo object mentioned previously, the resolveLookups method queries the
                  database using the site_id field as the search criteria. The method then copies the object ID of the
                  site found into the SiteVo object.

                  If the search criteria identifies zero rows or more than one row, BoContext.resolveLookups
                  throws a CboError exception (unless no search criteria was specified).

                  Note: If you specify an object ID as the search criteria, the resolveLookups method does not verify that
                  the object ID exists. The method just copies that object ID into the object ID field in the XVO. (The XVO has
                  separate fields for the object ID to use as search criteria and the actual object ID.)


                  You can use one of the following search criteria in an XVO to identify the existing row:
                      the object ID of the row (see Identifying a Row by Object ID on page 147)
                      a unique combination of fields enforced by a unique index (see Identifying a Row by Fields in a
                      Unique Index on page 149)
                      other combinations of fields (see Identifying a Row by Fields in Tables on page 151)
                      the external ID of the row, if the table has a relation to the extern_id table (for details on
                      external IDs and using external IDs in XVOs, see the Integration Gateway Implementation Guide)



146
                                                                          CHAPTER 3   Working with XVOs




For each type of search criteria, the XVOs provide setLookupBy<XXX> methods for setting the
search criteria in the XVO (for example, setLookupByObjid, setLookupByExternId, and
setLookupByIdIndex).

Important: If you call more than one setLookupBy<XXX> method (or if you call the same
setLookupBy<XXX> method again), the method clears any existing search criteria set by previous
setLookupBy<XXX> method calls. An XVO can use only one set of search criteria at a time. An XVO
cannot use a combination of search criteria set by multiple method calls.


Finally, for XVOs that represent views with unique fields, you can use the unique field to identify
the row represented by the XVO. For details, see Identifying a Row in a View on page 158.


Identifying a Row by Object ID
To identify an existing row by its object ID, you can use one of the following techniques:
   Create an XVO using the constructor that passes in an object ID or a unique field.
   XVOs for tables have a constructor for passing in an object ID. To identify the row represented
   by the XVO, you pass in the object ID of that row to the constructor.
   For example, the following statement creates a SiteVo object to represent the row identified by
   the object ID 268435457:
      ...
      String strObjectId = "268435457";
      ...
      // Create an XVO to represent an existing row. Use the object ID to
      // identify the row represented by the XVO.
      SiteVo voSite = new SiteVo(strObjectId);
      ...
   Create an XVO using the default constructor, and do the following:
   a. Create a new ClfyObjid object to specify the object ID.
   b. Call the setLookupByObjid method of the XVO, passing in the new ClfyObjid object.
   For example:
      // Create an XVO to represent an existing row. Use the object ID to
      // identify the row represented by the XVO.
      SiteVo voSiteExistingObjid = new SiteVo();
      voSiteExistingId.setLookupByObjid(new ClfyObjid("site", strObjectId));

When you call the BoContext.resolveLookups method (or a method that calls
resolveLookups, such as BoContext.updateAll) to resolve an XVO set up in this way, the
method does not query the database to verify that the specified object ID exists. Instead, the
method just copies that object ID into the object ID field in the XVO.

As a result, if the object ID is invalid or does not match any row in the database, no exception is
thrown. For example, suppose that you want to update an existing row in the
ClarifyCRM database. You create an XVO for an existing row and call setLookupByObjid,
passing in an invalid object ID. When you pass this XVO to the BoContext.updateAll method,
no exception is thrown because the specified row does not exist.




                                                                                                   147
Using an XVO to Identify an Existing Row




                  To avoid problems like this, you should avoid using the setLookupByObjid method unless you
                  are sure that you have a valid object ID (for example, if the object ID was returned in a query).

                  The following example of an EJB client sets up an XVO to represent an existing contact with a
                  specified ID. The example sets an updated phone number for the contact in the XVO before passing
                  the XVO to an EJB for an XBean.
                      import com.clarify.xvobase.*;
                      import com.clarify.xvo.*;
                      ...
                      // Create a ClfyObjid object to specify the object ID of the contact.
                      // strContactObjid is the object ID as a string value.
                      ClfyObjid contactObjid = new ClfyObjid("contact", strContactObjid);
                      // Create a new ContactVo and set the object ID as the criteria for
                      // identifying the existing contact in the database
                      ContactVo myContactVo = new ContactVo();
                      myContactVo.setLookupByObjid(contactObjid);
                      // Set an updated phone number for the contact in the XVO.
                      myContactVo.setPhone(strNewPhoneNumber);
                      // Pass the XVO to an EJB for your XBean to update the
                      // database.
                      myEJB.updateContact(strSessionId, myContactVo,
                           ... /* additional parameters */ );
                      ...

                  The following example is part of the preExecute method of the XBean used by the EJB in the
                  previous example. The preExecute method calls the BoContext.resolveLookups method to
                  copy the specified object ID into the objid field for the XVO.
                      import com.clarify.cbo.*;
                      import com.clarify.xvobase.*;
                      import com.clarify.xvo.*;
                      ...
                      protected void preExecute(BoContext boContext) {
                         ...
                         try {
                            // Update the XVO with the object ID of the contact.
                            boContext.resolveLookups(new Xvo[] {m_existingContactVo});

                              if ((m_existingContactVo == null) ||
                                 (m_existingContactVo.getObjid() == null)) {
                                 ...
                                 // Handle the situation in which m_existingContactVo is null or
                                 // no lookup criteria was specified in m_existingContactVo.
                                 ...
                              }

                          } catch(CboError e) {
                             ...
                             // Handle the situation in which the lookup criteria for
                             // m_existingContactVo does not match any existing contact or matches
                             // multiple contacts.
                             ...
                          }
                          ...
                          // Set up additional XVOs to find rows to relate to
                          ...
                          // Create a Contact BO to update the contact.


148
                                                                              CHAPTER 3   Working with XVOs




       Contact contactBo =
          (Contact) boContext.createBO("com.clarify.cbo.Contact");
       // Query the Contact BO to retrieve any fields required by the
       // BO for performing an update operation.
       contactBo.setDataFields("*");
       contactBo.setFilter("objid='" + m_existingContactVo.getObjid() + "'");
       contactBo.query();
       // Copy the changes from the XVO to the BO.
       contactBo.addVo(m_existingContactVo);
       ...
       // Save the changes to the database.
       boContext.updateAll();
       ...


Identifying a Row by Fields in a Unique Index
Some tables in the ClarifyCRM database have indexes that enforce uniqueness, based on a
combination of fields. For example, the contact table has an index named name_index, which
enforces the rule that no two rows can have the same combination of values in the first_name,
last_name, and phone fields.

For these combinations of fields, any one combination of values corresponds to at most one row in
the database. For example, given the first name Jane, the last name Smith, and the phone number
1-800-555-1212, at most one row in the contact table has this combination of values.

XVOs leverage this fact by allowing you to identify an existing row through a combination of fields
used in a unique index. For example, if you want to create a ContactVo object to represent an
existing contact, you can use the first name, last name, and phone number to identify the contact.

For each unique index for a table, there is a “lookup” class with a constructor and setter methods
for specifying the field values used to identify the row that the XVO represents. The lookup class is
named <table_name>LookupBy<index_name>.

In the XVO for the table with the unique index, there are methods for getting and setting this
lookup object in an XVO. The methods are named get/setLookupBy<index_name>.

Note: The lookup classes and accessor methods for lookup objects are not generated for XVOs for views.


As with XVO class names and accessor method names, the table name and index name in this class
name follow the same naming convention (removal of underscores and hyphens, and
capitalization of letters).

For example, if your custom table named x_custom_data has a unique index named
x_id_number_index, the corresponding lookup class name for the index is
XCustomDataLookupByXIdNumberIndex. In the XCustomDataVo class, the accessor methods
for this lookup class are named get/setLookupByXIdNumberIndex.

Note: Some indexes in the data dictionary use the naming convention I<type_id> (or example, the name
of the unique index on the offering table with the type ID 5759 is I5759). As a result, the corresponding
lookup class name for the index is OfferingLookupByI5759, and the XVO accessor methods for the
lookup object are get/setLookupByI5759. If the name of the index is not self-explanatory, consult the
Data Dictionary for Objects for details on the fields used in the unique index.



                                                                                                         149
Using an XVO to Identify an Existing Row




                  The XvoGen tool, which generates the XVO classes (as explained in Updating the XVO Classes on
                  page 161), automatically creates these classes and methods for any table with a unique index in the
                  ClarifyCRM data dictionary. If you customize the data dictionary to add your own table with a
                  unique index, the XvoGen tool will generate a class and methods for that unique index.

                  Note: If a unique index is based on a relation (in addition to the combination of fields), the XVO classes do
                  not provide (and the XvoGen tool does not generate) a LookupBy class for the index or get/
                  setLookupBy methods for that index.


                  To identify an existing row by a combination of fields, do the following:
                   1. Create a <table_name>LookupBy<index_name> object and pass in the values of the fields
                      to be used as search criteria.
                      Alternately, you can use the default constructor and then call the setter methods of the
                      <table_name>LookupBy<index_name> object to set the values of the fields to be used as
                      search criteria.
                   2. Call the setLookupBy<index_name> method of the XVO, passing in the
                      <table_name>LookupBy<index_name> object.

                  The following section of code is an example of an EJB client that sets up a ContactVo to represent
                  the contact with the first name Jane, the last name Smith, and the phone number 1-800-555-1212.
                  The example uses the lookup class named ContactLookupByNameIndex, which is based on the
                  first_name, last_name, and phone fields used in the name_index index.
                      import    com.clarify.xvobase.*;
                      import    com.clarify.xvo.*;
                      ...
                      String    strFirstName = "Jane";
                      String    strLastName = "Smith";
                      String    strPhone = "1-800-555-1212";

                      // Create a lookup object to identify the contact by first name,
                      // last name, and phone number.
                      ContactLookupByNameIndex contactLookup =
                           new ContactLookupByNameIndex(strFirstName, strLastName, strPhone);

                      //   Alternately, you can use the default constructor and call setter
                      //   methods to set the criteria for finding the contact.
                      //   ContactLookupByNameIndex contactLookup =
                      //        new ContactLookupByNameIndex();
                      //   contactLookup.setFirstName(strFirstName);
                      //   contactLookup.setLastName(strLastName);
                      //   contactLookup.setPhone(strPhone);

                      // Create a new ContactVo object for an existing contact.
                      ContactVo contactVo = new ContactVo();

                      // Set the lookup object in the ContactVo.
                      contactVo.setLookupByNameIndex(contactLookup);
                      ...




150
                                                                            CHAPTER 3   Working with XVOs




Identifying a Row by Fields in Tables
If you want to create an XVO for an existing row and identify the row based on a combination of
fields in the current table or related tables, you can define a named lookup. A named lookup
consists of the following information stored in the database under a unique name:
   the name of the table or view that the XVO represents
   the combination of fields that should be used to identify the existing row

The specified fields can also be from related tables. For fields from related tables, you define the
paths to those fields and an alias (or a “friendly name”) for those paths.

For example, suppose that you want to set up an XVO to represent an existing site where a
specified contact plays the Administrative role. You must define a named lookup for the site table
and specify the following fields as part of the named lookup:
   the role_name field in the contact_role row related to the site table through the
   site2contact_role relation
   In the definition of your named lookup, specify the path to this field from the site table:
          site2contact_role;role_name
   In the named lookup, you can use contact_role_name as the “friendly name” for this field.
   the first_name, last_name, and phone fields in the contact row related to the site table
   through the contact_role table
   In the definition of your named lookup, specify the paths to these fields from the site table:
          site2contact_role:contact_role2contact;first_name
          site2contact_role:contact_role2contact;last_name
          site2contact_role:contact_role2contact;phone
   In the named lookup, you can use contact_first_name, contact_last_name, and
   contact_phone as the “friendly names” for these fields.

You store named lookups in the database using the same data model for extended filters (the
definitions are stored in the xfilter table and the axfprops table). Since the Customization
Center provides an Extended Filter Editor, you can either use the Extended Filter Editor to define
your named lookups, or you can create .dat files for the lookups and use the Data Exchange
utility (dataex) to import the .dat files into your database.

Note that although named lookups and extended filters share the same data model, they differ
from each other in the following ways:
   For named lookups, the filter_type field in the xfilter table is set to 1. (For extended
   filters, this field is set to 0.)
   Named lookups are used by XVOs to identify a single row in the database. The combination of
   the fields specified in a named lookup should find a unique row. (In comparison, extended
   filters are designed to allow end users to search for and retrieve multiple rows.)
   When using a named lookup, you must provide values for all fields specified in the named
   lookup. (By contrast, for extended filters, these fields represent optional search criteria.)
   When you use a named lookup to find an existing row, the equality operator is used to compare
   the values that you specify against the values of the fields in the database. (For extended filters,
   the application can use comparison operators other than the equality operator.)



                                                                                                       151
Using an XVO to Identify an Existing Row




                  For information on extended filters, see the User Interface Framework Customization Guide manual,
                  the Customization Center Guide, and the Core Business Objects Reference Guide.

                  After you define your named lookups in the database, run the XvoGen utility to regenerate the
                  XVO classes. (For details, see Updating the XVO Classes on page 161.) XvoGen generates classes and
                  methods that allow you to use these named lookups to identify existing rows.

                  Note: XvoGen does not generate any named lookup classes or accessor methods for XVOs for views.


                  For each named lookup for a table, XvoGen generates a “lookup” class with setter methods for the
                  fields defined in the named lookup. For fields in related tables, the setter methods use the “friendly
                  name” of the field. The lookup class is named <table_name>LookupBy<lookup_name>.

                  In the XVO for the table with the named lookup, XvoGen defines methods for getting and setting
                  this lookup object in an XVO. The methods are named get/setLookupBy<lookup_name>.

                  Note: As with XVO class names and accessor method names, the table name and named lookup in this class
                  name and method names follow the same naming convention (removal of underscores and hyphens, and
                  capitalization of letters).


                  To use named lookups with XVOs, do the following:
                   1. Define a named lookup in the ClarifyCRM database.
                      You can add the named lookup in one of the following ways:
                         You can use the Extended Filter Editor in the Customization Center to define the named
                         lookup as an extended filter, and then you can change the value of the filter_type field to
                         change the extended filter to a named lookup.
                         For details, see Using the Customization Center to Define a Named Lookup on page 153.
                         You can define the named lookup in a .dat file and use the Data Exchange utility (dataex)
                         to import the named lookup into the database.
                         For details, see Using the Data Exchange Utility (dataex) to Import a Named Lookup on page 155.
                      When defining the named lookup, you specify the fields that should be used to uniquely
                      identify a row. These fields can be in the current table and in related tables. For fields in related
                      tables, specify the field in terms of a relation path to the field (for example,
                      site2contact_role;role_name). For all fields that you specify, define a “friendly name”
                      for that field (for example, contact_role_name for the field related through the path
                      site2contact_role;role_name).
                   2. Use the XvoGen tool to regenerate the XVO classes.
                      Because the database contains a named lookup, XvoGen generates the lookup class and
                      accessor methods for the named lookup. For details, see Updating the XVO Classes on page 161.




152
                                                                                  CHAPTER 3   Working with XVOs




3. In your code for setting up the XVO, create a <table_name>LookupBy<lookup_name>
   object, and pass in the values of the fields to be used as search criteria.
   See the javadoc for the XVO for the order in which you must specify the arguments. Note that
   the order in which you specified the fields when defining the named lookup affects the order of
   arguments in the constructor.
   Alternately, you can use the default constructor and then call the setter methods of the
   <table_name>LookupBy<lookup_name> object to set the values of the fields to be used as
   search criteria.
4. Call the setLookupBy<lookup_name> method of the XVO, passing in the
   <table_name>LookupBy<lookup_name> object.

For a complete example, see Example of Using Named Lookups on page 157.

Using the Customization Center to Define a Named Lookup
If you have installed and are using the Customization Center to perform your customizations, you
can use the Extended Filter Editor to define your named lookups (since both named lookups and
extended filters use the same data model).

The Customization Center Guide provides instructions for starting the Extended Filter Editor and
creating a new extended filter. You can follow the same instructions for defining your named
lookup, but keep the following guidelines in mind:
   The combination of fields that you specify should identify a row in the table for the XVO.
   For the name of your lookup, prepend the characters x_ to the name. This naming convention
   indicates that your named lookup is a custom addition to the database and minimizes the
   chances that in subsequent releases, new baseline extended filters use the same name.
   For the View or Table Name field, specify the name of the table for the XVO.
   You can ignore the controls in the Baseline Fields tab. Named lookups do not use the values that
   you set in these controls.
   In the Additional Properties tab, add a row for each field that you want used by the named
   lookup, and specify a “friendly name” for the field.
     For fields in the current table, specify the field name in the Absolute Path field.
     For fields in related tables, specify the absolute path to the field in the Absolute Path field.
     Start the path from the table for the XVO. Between relation names, use a colon as a delimiter.
     Between the name of the last relation and the name of the field at the end of the path, use a
     semi-colon as a delimiter. For example:
      site2contact_role:contact_role2contact;first_name
      site2contact_role;role_name
   The XvoGen utility generates a setter method in the lookup class for each field that you add
   here. The setter method is named after the “friendly name” of the related field.

   Note: Aside from the Friendly Name field and the Absolute Path field, you do not need to fill in the rest
   of the fields in the Additional Properties tab. Named lookups do not use the rest of these settings.




                                                                                                           153
Using an XVO to Identify an Existing Row




                  After you save your named lookup to the database, you must change the value of the
                  filter_type field for your new row in the xfilter table. Since the Extended Filter Editor is
                  designed to save extended filters, the value of the filter_type field for your new row is 0. You
                  must change this value for your new row to 1.

                  Note: Only change the value of the filter_type field for the rows that you add as named lookups. Do not
                  change the value of this field for existing extended filters.


                  For example, suppose that you want to create a named lookup for the site table that uses the
                  combination of the contact role name, first name, last name, and phone number to uniquely
                  identify a site.
                   1. In the Extended Filter Editor, click Create to create the new named lookup.
                   2. In the Filter Name field, enter a name for your named lookup, starting with the characters x_
                      (for example, x_contact_filter_for_site).
                   3. In the View Or Table Name field, enter site.
                   4. Click Search, select the site table from the list, and click Select.
                   5. Click the Additional Properties tab.

                      Note: You do not need to fill in any of the controls in the Baseline Fields tab. Named lookups do not use
                      these controls. Unlike extended filters, if you want to specify fields on the current table, you need to
                      specify them in the Additional Properties tab.

                   6. Add the following related fields by clicking the Add button and filling in the Friendly Name
                      and Absolute Path fields for each item:
                         Set contact_role_name as the friendly name for the following absolute path:
                              site2contact_role;role_name
                         Set contact_first_name as the friendly name for the following absolute path:
                              site2contact_role:contact_role2contact;first_name
                         Set contact_last_name as the friendly name for the following absolute path:
                              site2contact_role:contact_role2contact;last_name
                         Set contact_phone as the friendly name for the following absolute path:
                              site2contact_role:contact_role2contact;phone

                      Note: You do not need to fill in any of the other controls in this tab. Named lookups do not use these
                      other controls.

                   7. Click Save to save your named lookup to the database.
                   8. Use the Data Exchange utility (dataex) to import the following .dat file to change the value of
                      the filter_type field from 0 to 1 for your named lookup.
                      OBJECT TYPE="xfilter", NAME=my_named_lookup UNIQUE_FIELD=name
                           FIELDS
                              name ="x_contact_filter_for_site";




154
                                                                                   CHAPTER 3    Working with XVOs




           filter_type = 1;
        END_FIELDS
   END_OBJECT NAME=my_named_lookup
   For information on using the Data Exchange utility and on the .dat file format, see the manual
   System Administration Guide.

Using the Data Exchange Utility (dataex) to Import a Named Lookup
If you need to create a named lookup and cannot use the Customization Center, you can create a
.dat file for the named lookup and use the Data Exchange utility (dataex) to import the named
lookup into the ClarifyCRM database.

For information on using the Data Exchange utility and on the .dat file format, see the manual
System Administration Guide.

For each named lookup, you need to define the following records in the .dat file:
   Define an xfilter record for the named lookup. For a list of the fields that you need to specify,
   see Table 13.
   For each specified field that you want used as criteria to find the row, define an axfprops
   record and relate the record to the xfilter record for the named lookup through the
   axfprops2xfilter relation. For a list of the fields in the axfprops table that you need to
   specify, see Table 14.

Table 13 Fields in the xfilter table to specify when creating a named lookup
Field Name                Data Type                 Description
name                      char                      Specify a name for your named lookup in this field.
                                                    Prepend the characters x_ to the name. The x_ prefix
                                                    indicates that the named lookup is a custom record and
                                                    minimizes the chances that in a subsequent release, a
                                                    new baseline named lookup will use the same name.
                                                    When you run the XvoGen tool to generate the XVO
                                                    classes (see Updating the XVO Classes on page 161), the
                                                    name that you specify in this field is used as part of the
                                                    lookup class name
                                                    (<table_name>LookupBy<lookup_name>).
type_name                 char                      Specify the name of the table or view that you will use
                                                    this named lookup for (for example, contact or site).
filter_type               int                       Set the value of this field to 1 to indicate that this is a
                                                    named lookup (not an extended filter).




                                                                                                                  155
Using an XVO to Identify an Existing Row




                  Table 14 Fields in the axfprops table to specify when creating a named lookup
                   Field Name              Data Type                Description
                   friendly_name           char                     Specify an alias or “friendly name” for the field to use as
                                                                    criteria for finding the row.
                                                                    When you run the XvoGen tool to generate the XVO
                                                                    classes (see Updating the XVO Classes on page 161), a
                                                                    getter and setter method is generated in the lookup class
                                                                    for each axfprops record related to the named lookup.
                                                                    The name that you specify in the friendly_name field
                                                                    is used in names of the getter and setter methods.
                   path                    char                     For fields in the table for the named lookup, just specify
                                                                    the field name.
                                                                    For fields in related tables, specify the path to the related
                                                                    field from the table for the named lookup.
                                                                    Between relation names, use a colon as a delimiter.
                                                                    Between the last relation name and the field name, use a
                                                                    semi-colon as a delimiter.
                                                                    For example, the following is the path to the contact
                                                                    first_name field for a named lookup for the site table:
                                                                    site2contact_role:contact_role2contact;fi
                                                                    rst_name


                  The following is an example of a .dat file for a named lookup. The named lookup is for the site
                  table and uses the combination of the contact role name, first name, last name, and phone number
                  to uniquely identify a site.
                      OBJECT TYPE="xfilter", NAME=my_named_lookup UNIQUE_FIELD=name
                           FIELDS
                              name = "x_contact_filter_for_site";
                              type_name = "site";
                              filter_type = 1;
                           END_FIELDS
                      END_OBJECT NAME=my_named_lookup

                      OBJECT TYPE="axfprops", NAME=contact_role_name UNIQUE_FIELD=friendly_name
                      UNIQUE_RELATION=axfprops2xfilter
                           FIELDS
                              friendly_name = "contact_role_name";
                              path = "site2contact_role;role_name";
                           END_FIELDS
                           RELATIONS
                              TO_NAME = "my_named_lookup" REL="axfprops2xfilter";
                           END_RELATIONS
                      END_OBJECT NAME=contact_role_name

                      OBJECT TYPE="axfprops", NAME=contact_first_name UNIQUE_FIELD=friendly_name
                      UNIQUE_RELATION=axfprops2xfilter
                           FIELDS
                              friendly_name = "contact_first_name";
                              path = "site2contact_role:contact_role2contact;first_name";
                           END_FIELDS
                           RELATIONS


156
                                                                         CHAPTER 3   Working with XVOs




           TO_NAME = "my_named_lookup" REL="axfprops2xfilter";
        END_RELATIONS
   END_OBJECT NAME=contact_first_name

   OBJECT TYPE="axfprops", NAME=contact_last_name UNIQUE_FIELD=friendly_name
   UNIQUE_RELATION=axfprops2xfilter
        FIELDS
           friendly_name = "contact_last_name";
           path = "site2contact_role:contact_role2contact;last_name";
        END_FIELDS
        RELATIONS
           TO_NAME = "my_named_lookup" REL="axfprops2xfilter";
        END_RELATIONS
   END_OBJECT NAME=contact_last_name

   OBJECT TYPE="axfprops", NAME=contact_phone UNIQUE_FIELD=friendly_name
   UNIQUE_RELATION=axfprops2xfilter
        FIELDS
           friendly_name = "contact_phone ";
           path = "site2contact_role:contact_role2contact;phone";
        END_FIELDS
        RELATIONS
           TO_NAME = "my_named_lookup" REL="axfprops2xfilter";
        END_RELATIONS
   END_OBJECT NAME=contact_phone

Example of Using Named Lookups
The following section of code is an example of an EJB client that sets up a SiteVo to represent the
site where the contact Jane Smith (phone number 1-800-555-1212) plays the Administrator role. The
example uses the lookup class named SiteLookupByXContactFilterForSite, which is based
on the named lookup x_contact_filter_for_site. This named lookup is defined in the
examples in Using the Customization Center to Define a Named Lookup on page 153 and Using the Data
Exchange Utility (dataex) to Import a Named Lookup on page 155.
   import   com.clarify.xvobase.*;
   import   com.clarify.xvo.*;
   ...
   String   strFirstName = "Jane";
   String   strLastName = "Smith";
   String   strPhone = "1-800-555-1212";
   String   strContactRole = "Administrator";

   // Create a lookup object for the named lookup x_contact_filter_for_site
   // and set up the criteria for finding the site.
   SiteLookupByXContactFilterForSite siteLookup =
        new SiteLookupByXContactFilterForSite(strContactRole,
        strFirstName, strLastName, strPhone);
   // NOTE: The order of arguments in the constructor depends on the order
   // in which you specified the fields when you created the named lookup.
   // Check the javadoc for your generated XVO classes for the correct
   // order of arguments.

   // Create a new SiteVo object for an existing contact.
   SiteVo siteVo = new SiteVo();




                                                                                                  157
Using an XVO to Identify an Existing Row




                      // Set the lookup object in the SiteVo.
                      siteVo.setLookupByXContactFilterForSite(siteLookup);
                      ...


                  Identifying a Row in a View
                  Most views in the data dictionary have a field that is marked as the unique field. Each row in a
                  view should have a unique value for this field. (Note that there are exceptions to this, as explained
                  later in this section.)

                  In each XVO for a view with a unique field, there is a constructor that can pass in a value for the
                  unique field. To identify the row represented by the XVO, pass in a value that uniquely identifies
                  this row.

                  For example, in the empl_user view, the employee field is marked as unique. The employee
                  field in this view is the objid field from the employee table. The XVO for the empl_user view is
                  the EmplUserVo class.

                  To specify that an EmplUserVo object represents the row for the employee with the object ID
                  268435457, pass the object ID 268435457 to the EmplUserVo constructor, as shown below:
                      String strObjectId = "268435457";
                      ...
                      // Create an XVO to represent an existing row. Use the unique key to
                      // identify the row represented by the XVO.
                      EmplUserVo voEmplUser = new EmplUserVo(strObjectId);
                      ...

                  In the data dictionary, some of the fields that are marked as unique in a view are not truly unique.
                  For example, in the cas_secure view, the role_objid field (the objid field from the
                  contact_role table) is marked as unique. However, multiple rows in that view can have the
                  same value for the role_objid field.

                  Finally, some views do not have any fields marked as unique. The XVOs for these views only have
                  a default constructor.


                  Finding the Row for an XVO
                  If an input XVO is passed to your CBO application or component (such as an XBean), you must
                  pass the XVOs to the BoContext.resolveLookups method to resolve the XVOs.

                  This method uses the criteria in the XVOs to find the specified row in the database and copies the
                  object ID from the row to the XVO.

                  After you call this method, you can use the XVOs to update existing rows and establish relations.




158
                                                                                  CHAPTER 3   Working with XVOs




You can use the following signatures of the BoContext.resolveLookups method to resolve the
XVOs:
   If you just want to copy the object IDs of each row into the corresponding XVO, use the
   signature that passes in an array of XVOs:
       public void resolveLookups(Xvo[] xvos)
   If you want the method to copy values from other fields into an XVO, use the following
   signature on each input XVO:
       public void resolveLookups(Xvo vo, String fieldList)
   For the second argument, you can specify a comma-delimited list of the fields that you want
   retrieved.

Note: If the criteria in an XVO matches multiple rows, the BoContext.resolveLookups method
throws a CboError exception. Also, if the criteria matches zero rows, the method throws a CboError
exception, unless you are using the first signature and the search criteria is not specified or the XVO is null.


The following is an example of a method in an XBean. The setter methods of the XBean pass in
several XVOs that represent new and existing rows in the database. The method first calls the
resolveLookups method before using any of the XVOs.
   public class MyContactCreateXB extends com.clarify.cbo.XBeanImpl {

       protected void preExecute(BoContext boContext) { {

           try {

              // First find the existing rows in the database for all XVOs.
              boContext.resolveLookups(new Xvo[] {m_newContactVo,
                 m_newContactRoleVo, m_relatedSiteVo, m_relatedBusOrgVo});
              if ((m_relatedSiteVo == null) || (m_relatedSiteVo.getObjid() == null)) {
                 ...
                 // Handle the situation in which m_relatedSiteVo is null or
                 // no lookup criteria was specified in m_relatedSiteVo.
                 ...
              }
              if ((m_relatedBusOrgVo == null) ||
                 (m_relatedBusOrgVo.getObjid() == null)) {
                 ...
                 // Handle the situation in which m_relatedBusOrgVo is null or
                 // no lookup criteria was specified in m_relatedBusOrgVo.
                 ...
              }


           } catch(CboError e) {
              ...
              // Handle the situation in which the lookup criteria for
              // m_relatedSiteVo or m_relatedBusOrgVo does not match any existing
              // site or account or if the criteria matches multiple sites and
              // accounts.
              ...
           }
           ...



                                                                                                             159
Using XVOs for Views




                 Note: While you should not need to pass any XVOs for new rows to the resolveLookups method (since
                 these XVOs do not represent any existing rows), you should still do this as a general guideline. For new
                 rows, do not specify additional fields to retrieve. (If you specify a field list for a new row, an exception is
                 thrown.)




Using XVOs for Views
                 Some of the XVO classes represent rows in database views. These XVOs are not intended to be used
                 to update the database. You should consider the data in these XVOs are read-only.

                 In general, if you need to create an XVO to identify a row to use in a relation, you should create an
                 XVO for a table, not a view.

                 These XVO classes differ from XVO classes for tables in the following ways:
                       XVO classes for views do not have constructors for setting the mode of an XVO (see Creating an
                       XVO on page 127).
                       These constructors are not present because XVOs for views cannot be used to add new rows to
                       the database.
                       Because all views do not have the objid field that tables have, XVOs for views differ in the
                       following ways:
                       – These XVOs do not have constructors that pass in the object ID.
                         However, for views that have fields marked as unique, the XVOs have constructors that pass
                         in values for the unique fields. You can use these to identify the row represented by the view,
                         as explained in Identifying a Row in a View on page 158.
                       – The getObjid and setObjid methods are not available in XVO classes for views.
                       Many views have fields that hold object IDs. For example, a view might include the objid
                       fields from several joined tables.
                       In XVOs for views, the getter and setter methods for these object ID fields pass in and return the
                       object IDs as strings, not as ClfyObjid objects.
                       The only type of lookup classes that are applicable to XVO classes for views are lookup classes
                       based on named lookups. For more information about named lookups, see Identifying a Row by
                       Fields in Tables on page 151.
                       Certain views in the data dictionary do not have any field marked as the unique field (for
                       example, such as the svc_part_view2 view).
                       For the XVOs for these types of views, there is no constructor to identify the existing row than
                       the XVO represents. As a result, if you attempt to copy this XVO to a business object by calling
                       the BO.addVo method, an exception is thrown.
                       In general, if you are using the BO.addVo method to add an XVO for an existing row, you must
                       set up the XVO with the criteria for finding the existing row, as explained in Using an XVO to
                       Identify an Existing Row on page 146.




160
                                                                                      CHAPTER 3   Working with XVOs




Determining and Changing the Mode of an XVO
        Each XVO has a mode that indicates whether the XVO represents a new row or an existing row.
        The mode of the XVO is determined when you create the XVO (see Creating an XVO on page 127).

        The XVOs returned from business objects (through the BO.getVo and BO.getVoAll methods)
        and from the bulk query results in the ClfyBulk EJB (see the Integration Gateway Implementation
        Guide) have their modes set to indicate that the XVOs represent existing rows.

        To determine the mode of an XVO, call the isNewRow method of the XVO. To change the mode of
        an XVO, call the setNewRow method of the XVO.

        Important: If the XVO is cast as the base class com.clarify.xvobase.Xvo, you cannot call these
        methods. You must cast the XVO to its specific type (such as CaseVo, SiteVo, ContactVo, etc.) before
        you can call these methods.


        The following example determines if mySiteVo represents a new row:
           if (mySiteVo.isNewRow()) {
                // The XVO mode indicates that the XVO represents a new row.
                ...
           }

        The following example changes an XVO for an existing row to represent a new row:
           // Create a new SiteVo to represent an existing row
           mySiteVo = new SiteVo();
           // Change the mode so that the XVO represents a new row
           mySiteVo.setNewRow(true);

        Note: In general, you should set the mode of an XVO when you call the constructor to create the XVO. You
        should not change the mode of an XVO returned from search results or from a row in a business object.




Updating the XVO Classes
        If you make any of the following changes, you must regenerate the XVO classes if you want the
        XVO classes and method to reflect your changes:
           You add a new table, view, or field (or change the data type of a field) in the data dictionary.
           You add a new index for enforcing uniqueness to the data dictionary.
           You add your own named lookups to the database.
           You define a new relation between the extern_id table and a table to support external IDs for
           that table. For details on external IDs, see the Integration Gateway Implementation Guide.

        To regenerate the XVO classes, you use a tool called XvoGen that is included with all CBO Java
        applications. The following sections explain how to regenerate the XVO classes:
           Understanding How Changes Affect XVO Classes on page 162
           Running the XvoGen Tool on page 165
           Checking the Results of XvoGen on page 169


                                                                                                                161
Updating the XVO Classes




                 Understanding How Changes Affect XVO Classes
                 When you regenerate the XVO classes, methods for your new custom fields, unique indexes, and
                 named lookups are added to the XVO classes. In addition, new XVO classes are generated for your
                 new custom tables and named lookups.

                 The names of the generated XVO classes and methods are based on the names of the tables, views,
                 fields, indexes, and named lookups in the database with the following changes:
                    All characters except the first character in the name and the first character after an underscore or
                    hyphen is covered to lowercase.
                    Any underscores or hyphens are omitted from the name.

                 For example, suppose that you add a custom table named x_custom_data with a field named
                 x_id_number to the data dictionary. When you regenerate the XVO classes, a new Java class
                 named XCustomDataVo is created with the methods getXIdNumber and setXIdNumber.

                 Table 15 summarizes the effect of data dictionary changes on the XVO classes.
Table 15 Types of changes in the database that require regeneration of XVO classes
Type of Change        Change Reflected in Regenerated XVO           Example of Change
Made in Database      Classes
New table or view     New XVO class for that table or view is       Suppose that you add a new table named x_customer
                      generated.                                    to the data dictionary.
                                                                    When you regenerate the XVO classes, XvoGen creates a
                                                                    new XVO class named XCustomerVo for that new table.
New field in a table New getter and setter methods for that field   Suppose that you add these fields to a table or a view:
or view              are generated.
                                                                      x_string_data (variable string field)
                                                                      x_integer_data (long integer field)
                                                                    When you regenerate the XVO classes, XvoGen adds
                                                                    these new accessor methods to the XVO for the table or
                                                                    view:
                                                                      public String getXStringData()
                                                                      public void setXStringData(String value)
                                                                      public int getXIntegerData()
                                                                      public void setXIntegerData(int value)




162
                                                                                                   CHAPTER 3   Working with XVOs




Table 15 Types of changes in the database that require regeneration of XVO classes (Continued)
Type of Change       Change Reflected in Regenerated XVO               Example of Change
Made in Database     Classes
New index for        This generates a new Java lookup class            Suppose that you add a new index named x_my_index
enforcing            named                                             for the x_customer table. This index is based on a two
uniqueness           <table_name>LookupBy<index_name>                  string fields named x_customer_type and
                     for the new index. The Java lookup class has      x_customer_name.
                     a constructor that passes in values for all the   When you regenerate the XVO classes, XvoGen creates a
                     indexed fields. The class also has getter and     new class named XCustomerLookupByXMyIndex.
                     setter methods for each indexed field
                                                                       This lookup class has the following constructors:
                     In the XVO, this generates a
                     getLookupBy<index_name> method and                  public XCustomerLookupByXMyIndex()
                     a setLookupBy<index_name> method                    public XCustomerLookupByXMyIndex(String
                     that pass in and return an object of the new        xCustomerType, String xCustomerName)
                     lookup class.
                                                                       This lookup class has the following new accessor
                                                                       methods for the indexed fields:
                                                                         public String getXCustomerName()
                                                                         public void setXCustomerName(String
                                                                         value)
                                                                         public String getXCustomerType()
                                                                         public void setXCustomerType(String
                                                                         value)
                                                                       In addition, XvoGen creates the following methods in the
                                                                       XCustomerVo class (the XVO for the x_customer
                                                                       table):
                                                                         public XCustomerLookupByXMyIndex
                                                                         getLookupByXMyIndex()
                                                                         public void setLookupByXMyIndex(
                                                                         XCustomerLookupByXMyIndex value)




                                                                                                                            163
Updating the XVO Classes




Table 15 Types of changes in the database that require regeneration of XVO classes (Continued)
Type of Change        Change Reflected in Regenerated XVO            Example of Change
Made in Database      Classes
New named lookup This generates a new Java lookup class              Suppose that you add a new named lookup named
                 named                                               x_my_named_lookup based on a two string fields. The
                 <table_name>LookupBy<lookup_name>                   named lookup uses the friendly names x_field and
                 for the new named lookup. The Java lookup           x_related_field for these fields.
                 class has a constructor that passes in values       When you regenerate the XVO classes, XvoGen creates a
                 for all the lookup fields. The class also has       new class named
                 getter and setter methods for each lookup           XCustomerLookupByXMyNamedLookup.
                 field.
                                                                     This lookup class has the following constructors:
                      In the XVO, this generates a
                      getLookupBy<lookup_name> method and              public XCustomerLookupByXMyNamedLookup()
                      a setLookupBy<lookup_name> method                public
                      that pass in and return an object of the new     XCustomerLookupByXMyNamedLookup(String
                      lookup class.                                    xField, String xRelatedField)
                                                                     This lookup class has the following new accessor
                                                                     methods for the fields in the named lookup:
                                                                       public String getXField()
                                                                       public void setXField(String value)
                                                                       public String getXRelatedField()
                                                                       public void setXRelatedField(String
                                                                       value)
                                                                     In addition, XvoGen creates the following methods in the
                                                                     XCustomerVo class (the XVO for the x_customer
                                                                     table):
                                                                       public XCustomerLookupByXMyNamedLookup
                                                                       getLookupByXMyNamedLookup()
                                                                       public void setLookupByXMyNamedLookup(
                                                                       XCustomerLookupByXMyNamedLookup value)
Added a relation      In the XVO for that table, this generates a    Suppose that you add a relation between the
from a table to the   getExternId method, a setExternId              x_customer table and the extern_id table.
extern_id table       method, a getLookupByExternId method,          When you regenerate the XVO classes, XvoGen creates
                      and a setLookupByExternId method.              the following new accessor methods for the
                      These methods pass in and return a             XCustomerVo class (the XVO for the x_customer
                      ClfyExternId object.                           table):
                                                                       public ClfyExternId getExternId()
                                                                       public void setExternId(ClfyExternId
                                                                       externId)
                                                                       public ClfyExternId
                                                                       getLookupByExternId()
                                                                       public void
                                                                       setLookupByExternId(ClfyExternId
                                                                       externId)


                 When generating accessor methods for new fields, XvoGen uses the Java types listed in
                 Understanding the Java Types Used for Database Data Types on page 130 for the values passed to and
                 returned from the accessor methods.


164
                                                                            CHAPTER 3   Working with XVOs




Running the XvoGen Tool
As mentioned previously, you use the XvoGen tool to regenerate, recompile, and repackage the
XVO classes. XvoGen is a CBO Java application. The classes for XvoGen are provided in the
ClfyCore.jar file, which is included with every CBO Java application.

There are two ways in which you can run the XvoGen tool:
   If you are using the Customization Center, you can click the Regenerate ClfyXvo.jar toolbar
   button.
   When you click this button, the tool regenerates the XVO classes, compiles the classes, and
   updates the ClfyXvo.jar file under the clfy_app_root/WEB-INF/lib directory. (For an
   explanation of the clfy_app_root directory, see Convention for Referring to Deployed Files on
   page 20.)
   For details on using the Customization Center, see the Customization Center Guide.
   You can also run the XvoGen tool from the command line.

The rest of this section explains how to run the XvoGen tool from the command line.

Note: When you run the XvoGen tool from the command line, you need to configure your environment to
run CBO applications before your can run this tool. For example, on UNIX, you need to set your PATH and
shared library path (for example, LD_LIBRARY_PATH, SHLIB_PATH, or LIBPATH) environment
variables to include the directories required by CBO. For details, see the System Administration Guide.


In order to run the XvoGen tool, you must have the required version of the JDK installed on your
system. (To determine the required version, see the platform guide on ClearAnswer web site at
http://clearanswer.clarify.com.)

In addition, you must include the following JAR files in your classpath:
   the JDK tools.jar file, which is located at <jdk_directory>\lib\tools.jar (required
   only if you use the -jardir option, which compiles the Java source files and packages them in
   a JAR file)
   ClfyCore.jar

Aside from the first JAR file, the rest of the JAR files are deployed with your CBO application. See
the documentation for your CBO application for the location of these JAR files.

The following JAR files are used by the XvoGen tool but do not need to be specified explicitly in the
classpath, provided that the files are in the same directory as the ClfyCore.jar file.
   velocity-dep-1.3.jar

To run the XvoGen tool, use the following command:
   java -cp <classpath> com.clarify.igb.tools.XvoGen
        -user <login_name> -password <password> -o <source_output_dir>
        [-jardir <jar_output_dir>] [-deleteall]
        [-tables <table_names>|@<filename> ] [-custom] [-help] [-info]

The command-line parameters are summarized in Table 16.




                                                                                                     165
Updating the XVO Classes




                 Table 16 XvoGen command-line parameters
                  Command-Line Parameter                   Description
                  -cp <classpath>                          You must specify the JAR files listed previously. (You can
                                                           set the CLASSPATH variable instead of using this
                                                           command-line parameter.)
                  -user <login_name>                       Login name for connecting to the ClarifyCRM database
                  -password <password>                     Password for connecting to the ClarifyCRM database.
                  -o <source_output_dir>                   Output directory where you want to generate the Java
                                                           source files for the XVO classes
                                                           If this directory does not already exist, XvoGen creates the
                                                           directory.
                  -jardir <jar_output_dir>                 (Optional) Indicates that you want to compile the Java
                                                           source files and package the class files in a ClfyXvo.jar
                                                           file in the specified directory
                                                           If this directory does not already exist, XvoGen creates the
                                                           directory.
                                                           If this directory already contains a ClfyXvo.jar file
                                                           from a previous run, XvoGen replaces the existing file.
                                                           If you specify this option, you must delete any existing
                                                           source files or specify the -deleteall option.
                  -deleteall                               (Optional) Indicates that you want to delete all files and
                                                           subdirectories (including any that you may have
                                                           manually added) in the
                                                           <source_output_dir>\com\clarify\xvo directory.
                                                           If you specify the -jardir option and there are files in
                                                           this directory, you must do one of the following:
                                                             To keep the existing files in that directory, use the -o
                                                             option specify a different source output directory.
                                                             To delete all files and subdirectories in that directory,
                                                             specify the -deleteall option. This deletes all files,
                                                             not just the Java source files.
                                                           Note: If you omit the -jardir and -deleteall options
                                                           and there are files in this directory, XvoGen just replaces
                                                           the existing XVO Java source files as they are generated.




166
                                                                               CHAPTER 3   Working with XVOs




Table 16 XvoGen command-line parameters (Continued)
Command-Line Parameter                          Description
-tables <table_names> | @<filename>             (Optional) Specifies that you want to generate XVOs and
                                                lookup classes only for the specified tables and views.
                                                You can specify a list of tables and views in one of the
                                                following ways:
                                                  You can specify a comma-delimited list of the table and
                                                  view names. Do not include spaces between the
                                                  commas.
                                                  You can specify the path to a plain text file containing
                                                  the table and view names.
                                                  In the file, the names must be delimited by whitespaces
                                                  (for example, by a space character or a carriage return).
                                                  To include a comment in the file, use a # character at the
                                                  start of the line containing the comment.
                                                If you specify the -custom option together with this
                                                option, this option is ignored.
-custom <custom_nondefault_file>                (Optional) Indicates that you want to generate the custom
                                                non-default XVOs that are defined in the specified file.
                                                When you specify this option, only the XVOs in this file
                                                are generated. Default XVOs for the tables and views in
                                                the ClarifyCRM database are not generated.
                                                For details, see Generating and Using Custom XVOs on
                                                page 172.
-help                                           (Optional) Prints out a help message describing the syntax
                                                of the command and the command-line arguments
-info                                           (Optional) Prints out additional instructions on running
                                                this tool (as is the case with the -help option, -info does
                                                not actually run the tool)


The XvoGen tool generates the Java source files for the XVO classes in the directory specified by the
-o option. If you also want the XvoGen tool to compile the XVO Java source files and package the
resulting classes in a ClfyXvo.jar file, specify the -jardir option and the directory where the
ClfyXvo.jar file should be created.

For the options that specify a directory or filename (such as -o and -jardir), you can specify an
absolute path or a relative path. To specify a relative path, start the path with a "." or ".." (for
example, ".\my_xvo_source_dir").

If you specify the -jardir option and there are existing files in the com\clarify\xvo directory
(for example, XVO Java source files from a previous run of the tool), you must either remove the
files or change your output directory. See the -deleteall option in Table 16 for details.

Important: Specifying the -deleteall option removes all files and subdirectories in the generated
com\clarify\xvo directory (not just the Java source files).


If you only want to generate the XVOs for selected tables and views, you can use the -tables
option to specify the names of those tables and views.



                                                                                                           167
Updating the XVO Classes




                 The following is an example of part of a .cmd file for running the XvoGen tool on Windows.
                 (Manual line breaks have been added to make the example more readable.) This example assumes
                 that you are using the copy of CBO and JAR files that are deployed with the ClarifyCRM web
                 applications.
                    echo off
                    SETLOCAL
                    ...
                    set JDK_DIR=<path to JDK directory>
                    set PATH=<install_dir>\Clarify\bin;%JDK_DIR%\bin;%PATH%
                    rem Variable used to avoid having to repeat long paths to JAR files
                    set JARDIR=clfy_app_root\WEB-INF\lib

                    rem Running XvoGen to generate, compile, and package the XVO classes.
                    java -cp %JARDIR%\ClfyCore.jar;%JDK_DIR%\lib\tools.jar
                         com.clarify.igb.tools.XvoGen -user sa -password mypassword
                         -o C:\xvo_source_files -jardir C:\xvo_jar_file -deleteall
                    ...
                    ENDLOCAL
                    echo on

                 Note: The above example specifies the -deleteall option, which deletes all files and subdirectories in the
                 com\clarify\xvo directory before regenerating the XVO source files.


                 The following example generates the XVOs and lookup classes only for the contact table, the
                 site table, and the empl_user view:
                    ...
                    rem Running XvoGen to generate, compile, and package the XVO classes.
                    java -cp %JARDIR%\ClfyCore.jar;%JDK_DIR%\lib\tools.jar
                         com.clarify.igb.tools.XvoGen -user sa -password mypassword
                         -o C:\xvo_source_files -tables contact,site,empl_user
                    ...

                 The following example uses a text file to specify the names of the tables and views for which XVOs
                 should be generated:
                    ...
                    rem Running XvoGen to generate, compile, and package the XVO classes.
                    java -cp %JARDIR%\ClfyCore.jar;%JDK_DIR%\lib\tools.jar
                         com.clarify.igb.tools.XvoGen -user sa -password mypassword
                         -o C:\xvo_source_files -tables @..\xvocfg\my_tables_list.txt
                    ...

                 In this example, the contents of the my_tables_list.txt file is shown below:
                    address
                    case
                    #employee
                    empl_user
                    #user

                 Since the table and view names just need to be separated by whitespace characters, you can also
                 use a space instead of a carriage return to delimit the names.




168
                                                                              CHAPTER 3   Working with XVOs




If you want to temporarily exclude the XVO for any specified table or view from being generated,
you can add a comment character (#) in front the line for the table or view name.
   address
   case
   #employee
   empl_user
   #user

The following is an example of part of a Bourne shell script for running the XvoGen tool on UNIX.
(Manual line breaks have been added to make the example more readable.) This example assumes
that you are using the copy of CBO and JAR files that are deployed with the ClarifyCRM web
applications.
   #!/bin/sh
   ...
   # Set all the environment variables required by CBO. See the
   # script files envvars.sh and envvars.csh for the variables that need
   # to be set. For details, see the manual System Administration Guide.
   ...
   JDK_DIR=<path to JDK directory>
   PATH=$JDK_DIR/bin:$PATH
   export PATH

   # Variable used to avoid having to repeat long paths to JAR files.
   JARDIR=clfy_app_root/WEB-INF/lib

   # Running XvoGen to generate, compile, and package the XVO classes
   java -cp $JARDIR/ClfyCore.jar:$JDK_DIR/lib/tools.jar
        com.clarify.igb.tools.XvoGen -user sa -password mypassword
        -o /mass1/xvo_source_files -jardir /mass1/xvo_jar_file -deleteall

Note: The above example specifies the -deleteall option, which deletes all files and subdirectories in the
com/clarify/xvo directory before regenerating the XVO source files.


Note that if you omit the -jardir option, you do not need to include ClfyCore.jar and
tools.jar in your classpath:
   rem Running XvoGen to generate the XVO source files.
   rem Since the source files are not compiled and packaged,
   rem the classpath does not need to include ClfyCore.jar and tools.jar
   java -cp $JARDIR/ClfyCore.jar com.clarify.igb.tools.XvoGen
        -user sa -password mypassword -o /mass1/xvo_source_files

For more explanation on install and deploy paths, see Convention for Referring to Deployed Files on
page 20.


Checking the Results of XvoGen
If XvoGen runs successfully, the following output appears in the window where you ran the tool:
   Loaded JDispLoad
      Registered Sales Interface
      Registered Analytics Interface (appears on Windows only)
   Logging into Clarify <database_name> database

   Java source dir : C:\xvo_source_files


                                                                                                       169
Updating the XVO Classes




                    Output jar           : C:\xvo_jar_file\ClfyXvo.jar
                    Log file             : C:\working_dir\XvoGen.log

                    Starting code generation.........................................
                    Created <number_of_generated_class_files> java files.
                    Time required for this run: <number_of_seconds_to_run> sec

                 In the example above:
                    The Class files dir and Output jar lines do not appear if you do not specify the
                    -jardir option.
                    The source output directory (specified by the -o option) is C:\xvo_source_files. (The Java
                    source files are generated in C:\xvo_source_files\com\clarify\xvo.)
                    The JAR file output directory (specified by the -jardir option) is C:\xvo_jar_file.
                    The XvoGen command was run from the directory C:\working_dir.

                 If an error occurs, you may see the following error message or other kinds of error messages:
                    Source code generation / compilation / jar generation has failed partially.
                    Check log for more information.

                 In this type of situation, you should check the XvoGen.log file, which is generated in the directory
                 where you ran XvoGen. Scan the log file for the keywords "[error]" and "[warn]". For example:
                    Wed Oct 29 09:35:55 PST 2004 [error] <error_message>
                    Wed Oct 29 09:35:55 PST 2004 [warn] <warning_message>

                 The error message may direct you to the CompilerMsg_XvoDbGen.log log file for more
                 information on the errors.

                 The following list summarizes some of the possible error messages that you may encounter:
                    In the terminal window where you are running XvoGen, you see the following error message:
                    Exception in thread "main" java.lang.NoClassDefFoundError:
                         com/clarify/igb/tools/XvoGen
                    Verify that the ClfyCore.jar file is in your classpath.
                    In the terminal window where you are running XvoGen, you see the following error message:
                    Must specify -deleteall option while generating jar file to permit cleanup of
                    source dir.
                    Run XvoGen with -help or -info option for more information.
                    As explained in Running the XvoGen Tool on page 165, you need to either specify a different
                    output directory for the source files (using the -o option) or remove all files and subdirectories
                    from the com\clarify\xvo directory (using the -deleteall option).
                    In the XvoGen.log file, you see the following error message:
                    Wed Oct 29 10:08:55 PST 2003         [error] Could not delete the old jar file.
                    Check that no other process is locking and using the ClfyXvo.jar file. (For example, you may
                    have deployed an EJB using the ClfyXvo.jar file that XvoGen is trying to replace.)
                    In the CompilerMsg_XvoDbGen.log file, you see the following error message:




170
                                                                               CHAPTER 3   Working with XVOs




   The system is out of resources.
   Consult the following stack trace for details.
   java.lang.OutOfMemoryError
   If the messages above are java.lang.OutOfMemoryError messages, run CodeGenerator
   with the -Xmx option. For eg, -Xmx256M.
   Run the XvoGen tool again, passing the -Xmx option with the amount of memory that you want
   to allocate to the java command. For example (see the bold text):
   java -cp <classpath> -Xmx256M com.clarify.igb.tools.XvoGen ...
   In the terminal window where you are running XvoGen, you see the following error message:
   Starting code generation..........................
   Source code generation / compilation / jar generation has failed partially.
   Check log for more information.
   In the XvoGen.log file, you see the following error message:
   Sun Oct 31 10:34:05 PST 2004 [error] Compilation failed for generator class
   XvoGen. Check compilation output in file C:\working_dir\CompilerMsg_XvoGen.log
   Sun Oct 31 10:34:05 PST 2004 [warn] Xvo class compilation of some source files
   may have failed. Jar file creation skipped..
   In the CompilerMsg_XvoDbGen.log file, you might see one of the following error messages:
   – If you see the following error, verify that the ClfyCore.jar file is in your classpath.
       C:\xvo_source_files\com\clarify\xvo\CaseVo.java:14: cannot access
          amdocs.core.vo.VoBase
       file amdocs\core\vo\VoBase.class not found
       public class CaseVo extends com.clarify.xvobase.Xvo
                     ^
       ... (other symbol resolution errors) ...
   – If you see the following error, verify that the ClfyCore.jar file is in your classpath.
       C:\xvo_source_files\com\clarify\xvo\CaseVo.java:10: package
       com.clarify.xvobase does not exist
       import com.clarify.xvobase.*;
       ^
       ... (other symbol resolution errors) ...
   In the XvoGen.log file, you see the following warning message:
   Sun Oct 31 10:54:12 PST 2004 [warn] Cannot file table conttact in database schema
   information. Table skipped.
   Check for typos in the names of the tables and views that you specified.

Note: If no <table>LookupBy classes and methods were generated for your named lookups and you used
the Extended Filter Editor in the Customization Center to define your named lookups, make sure that you set
the filter_type field to 1 in the rows for your named lookups. See Step 8 on page 154.




                                                                                                        171
Generating and Using Custom XVOs




Generating and Using Custom XVOs
                By default, each table or view in the ClarifyCRM database has a corresponding default XVO class.
                This XVO class is generated automatically by the XvoGen tool (see Updating the XVO Classes on
                page 161) and follows these rules:
                    The default XVO is named after the table or view (as explained in Understanding the Naming
                    Convention for XVOs on page 127).
                    The default XVO provides accessor methods for all fields in the table or view. Each accessor
                    method is named after the field.
                    The default XVO provides accessor methods for the lookup classes for each unique index and
                    named lookup. (For the default XVO for a view, the accessor methods for lookup classes are
                    generated only for the named lookups for the view.)

                Some ClarifyCRM applications may provide additional XVOs called non-default XVOs. A
                non-default XVO also represents a table or view in the ClarifyCRM database and differs from the
                default XVO in the following ways:
                    A non-default XVO can have any name. The name is not necessarily based on the table or view
                    represented by the XVO.
                    A non-default XVO can encapsulate and provide accessor methods for a subset of fields in the
                    table or view. The names of these accessor methods are not necessarily based on the names of
                    the fields.
                    A non-default XVO can provide a subset of accessor methods for the lookup classes for each
                    unique index and named lookup.

                You can also define your own custom non-default XVOs for tables and views in the
                ClarifyCRM database. You can define these if you want to provide a simpler programmatic
                interface by omitting accessor methods that you do not use.

                For example, you can define a custom non-default XVO for the contact table that only encapsulates
                the first_name, last_name, and phone fields. You can change the names of the accessor
                methods for the last_name field from getLastName/setLastName to getFamilyName/
                setFamilyName.

                Instead of writing these XVO classes in Java code, you define a configuration file that specifies the
                following information:
                    the name that you want to assign to the custom non-default XVO class
                    the fields to include or exclude in the XVO
                    different names for any of the accessor methods for the fields
                    the accessor methods for lookup classes that should be excluded

                Then, you use the XvoGen tool to generate the Java source files for these custom non-default XVOs.

                The following sections explain more details about how to generate custom non-default XVOs:
                    Creating the XVO Definition File on page 173
                    Generating the Custom XVO Classes on page 175
                    Compiling the Custom XVO Classes on page 176
                    Example of Defining a Custom XVO on page 176


172
                                                                        CHAPTER 3   Working with XVOs




Creating the XVO Definition File
To specify the custom non-default XVOs that you want generate, you must create an XML file that
uses the following format:
   <CustomNonDefaultXvoClasses PackageName="name_of_package_for_custom_xvos">

      <!-- Specify a Vo tag for each custom non-default XVO to generate -->
      <Vo Name="custom_xvo_name" DBObjectName="table_or_view_name"
         IncludeFields="list_of_fields_to_include"
         ExcludeFields="list_of_fields_to_exclude"
         ExcludeLookups="list_of_indexes_and_lookups_to_exclude">

          <!-- To change any getter/setter method names, use a PropertyMap tag -->
          <PropertyMap>

              <!-- Specify a field tag for each getter/setter method to rename -->
              <Field Name="original_name" JavaMethod="new_name_for_property" />
              ... ( additional Field tags ) ...

          </PropertyMap>

      </Vo>

      ... ( additional Vo tags ) ...

   </CustomNonDefaultXvoClasses>

For an example of this file, see Example of Defining a Custom XVO on page 176.

In this XML file:
1. Specify the <CustomNonDefaultXvoClasses> tag as the root tag of the file.
2. Set the PackageName attribute of this tag to the name of the Java package that should contain
   the generated custom non-default XVOs.

   Note: Do not use com.clarify as the beginning of the package name.


3. For each custom non-default XVO that you want to generate, specify a <Vo> tag.
   a. Set the Name attribute of the <Vo> tag to a name that you want to assign to this XVO. The
      XVO class uses this name followed by the Vo suffix ([Name]Vo.class).
     For example, suppose that you specify the following tag:
      <Vo Name="MyContact" ...
     This generates an XVO class named MyContactVo.class.
   b. Set the DBObjectName attribute of the <Vo> tag to the name of the table or view that this
      XVO represents.




                                                                                                  173
Generating and Using Custom XVOs




                    c. Set the following attributes of the <Vo> tag to specify the database fields that should be
                       encapsulated in the XVO:

                         If you want the XVO to encapsulate a selected set of fields, specify a comma-delimited list
                         of the field names in the IncludeFields attribute of the <Vo> tag.
                         For example, the following tag generates an XVO for the contact table that encapsulates
                         only the first_name, last_name, and phone fields:
                           <Vo Name="MyContact" DBObjectName="contact"
                              IncludeFields="first_name,last_name,phone" ...

                         If instead you want the XVO to encapsulate all fields except for a selected set of fields,
                         specify a comma-delimited list of the field names to exclude in the ExcludeFields
                         attribute of the <Vo> tag.
                         For example, the following tag generates an XVO for the contact table that encapsulates
                         all fields except for the mdbk and arch_ind fields:
                           <Vo Name="MyContact" DBObjectName="contact"
                              ExcludeFields="mdbk,arch_ind" ...
                      If both the IncludeFields and ExcludeFields attributes specify field names, the
                      generated XVO encapsulates only the fields in the IncludeFields attribute. Any fields
                      listed in both the IncludeFields and ExcludeFields attributes are excluded from the
                      XVO.
                    d. If you want to exclude certain lookups (for example, some of the lookups by unique indexes
                       or named lookups), specify a comma-delimited list of the names of those unique indexes and
                       lookups in the ExcludeLookups attribute of the <Vo> tag.
                      For example, the following tag generates an XVO for the contact table that does not
                      provide a lookup class for the name_index unique index:
                       <Vo Name="MyContact" DBObjectName="contact"
                          ExcludeLookups="name_index" ...
                    e. If you want to change the names of the getter and setter methods for any of the fields, add a
                       <PropertyMap> tag in the <Vo> tag.
                      In the <PropertyMap> tag, specify a <Field> tag for each of these fields:

                         Use the Name attribute of the <Field> tag to specify the name of the field.

                         Use the JavaMethod attribute of the <Field> tag to specify a new name to append to the
                         get/set prefixes.
                      For example, the following tag generates an XVO in which the getter and setter methods for
                      the first_name field are named getFName and getFName:
                       ...
                       <Vo ... >
                          <PropertyMap>
                             <Field Name="first_name" JavaMethod="Fname" />
                             ... <!-- additional Field tags -->
                          </PropertyMap>
                       </Vo>
                       ...




174
                                                                            CHAPTER 3   Working with XVOs




In the XML file, the following tags and attributes are optional:
   In the <Vo> tag, the IncludeFields, ExcludeFields, and ExcludeLookups attributes are
   optional. If you are not using an attribute, you can either omit the attribute or set the value to an
   empty string.
   The <PropertyMap> tag and the <Field> tag are also optional. If you do not want to rename
   any of the getter or setter methods, you can omit these tags.


Generating the Custom XVO Classes
After you create an XML file that specifies the custom non-default XVO classes to generate, you run
the XvoGen tool to generate the class files for these XVOs. For general instructions on running the
XvoGen tool, see Running the XvoGen Tool on page 165.

When you run this tool, specify the -custom option, and pass the path to your XML file as the
argument.

Important: Running the XvoGen tool with the -custom flag only generates the XVOs defined in the
specified XML file. The tool does not generate any default classes for the tables and views in the
ClarifyCRM database.


The following is an example of part of a .cmd file for using the XvoGen tool on Windows to
generate custom XVO classes. (Manual line breaks have been added to make the example more
readable.) This example assumes that you are using the copy of CBO and JAR files that are
deployed with the ClarifyCRM web applications.

This example assumes that your XML file is named MyCustomNonDefaultXvos.xml and is
located in a subdirectory named xvocfg (under the directory where you are running the tool).
   echo off
   SETLOCAL
   ...
   set JDK_DIR=<path to JDK directory>
   set PATH=<install_dir>\Clarify\bin;%JDK_DIR%\bin;%PATH%
   rem Variable used to avoid having to repeat long paths to JAR files
   set JARDIR=clfy_app_root\WEB-INF\lib

   rem Running XvoGen to generate the custom non-default XVO classes defined in
   rem the file xvocfg\MyCustomNonDefaultXvos.xml.
   java -cp %JARDIR%\ClfyCore.jar;%JDK_DIR%\lib\tools.jar
        com.clarify.igb.tools.XvoGen -user sa -password mypassword
        -o C:\xvo_source_files -custom xvocfg\MyCustomNonDefaultXvos.xml
   ...
   ENDLOCAL
   echo on

The following is an example of part of a Bourne shell script for using the XvoGen tool on UNIX to
generate custom XVO classes. (Manual line breaks have been added to make the example more
readable.) This example assumes that you are using the copy of CBO and JAR files that are
deployed with the ClarifyCRM web applications.

This example assumes that your XML file is named MyCustomNonDefaultXvos.xml and is
located in a subdirectory named xvocfg (under the directory where you are running the tool).



                                                                                                     175
Generating and Using Custom XVOs




                    #!/bin/sh
                    ...
                    # Set all the environment variables required by CBO. See the
                    # script files envvars.sh and envvars.csh for the variables that need
                    # to be set. For details, see the manual System Administration Guide.
                    ...
                    JDK_DIR=<path to JDK directory>
                    PATH=$JDK_DIR/bin:$PATH
                    export PATH

                    # Variable used to avoid having to repeat long paths to JAR files.
                    JARDIR=clfy_app_root/WEB-INF/lib

                    # Running XvoGen to generate, compile, and package the XVO classes
                    java -cp $JARDIR/ClfyCore.jar:$JDK_DIR/lib/tools.jar
                         com.clarify.igb.tools.XvoGen -user sa -password mypassword
                         -o /mass1/xvo_source_files -custom xvocfg/MyCustomNonDefaultXvos.xml


                Compiling the Custom XVO Classes
                After you run the XvoGen tool to generate your custom XVO classes, you can compile the classes
                and package them in a JAR file.

                Note: The XvoGen tool does not automatically compile and package custom XVO classes. You need to do
                this manually.


                When compiling the custom XVO classes, include the ClfyCore.jar file in your classpath.


                Example of Defining a Custom XVO
                Suppose that you wanted to define two custom non-default XVOs for the contact table:
                    For one XVO, you want the class name to be com.mycompany.custom.xvos.MyContactVo.
                    In this XVO, you want to encapsulate only the following fields:
                    – first_name
                    – last_name
                    – phone
                    – fax_number
                    – mobile_phone
                    – e_mail
                    You also want to change the names of the following accessor methods:
                    – For the phone field, you want the accessor methods named getMainNumber and
                      setMainNumber.
                    – For the mobile_phone field, you want the accessor methods named getCellNumber and
                      setCellNumber.




176
                                                                         CHAPTER 3   Working with XVOs




   – For the e_mail field, you want the accessor methods named getEmail and setEmail (as
     opposed to the default names getEMail and setEMail).
   For the other XVO, the class name should be
   com.mycompany.custom.xvos.MyOtherContactVo.
   In this XVO, you want to encapsulate all fields except for the following:
   – mdbk
   – arch_ind
   You also want to omit the lookup class that is normally generated for the name_index unique
   index for the contact table.

To generate these custom non-default XVOs, you create an XML file with the following contents:
   <CustomNonDefaultXvoClasses PackageName="com.mycompany.custom.xvos">
      <Vo Name="MyContact" DBObjectName="contact" IncludeFields="first_name,
         last_name,phone,fax_number,e_mail,mobile_phone">
         <PropertyMap>
            <Field Name="phone" JavaMethod="MainNumber" />
            <Field Name="mobile_phone" JavaMethod="CellNumber" />
            <Field Name="e_mail" JavaMethod="Email" />
         </PropertyMap>
      </Vo>
      <Vo Name="MyOtherContact" DBObjectName="contact"
         ExcludeFields="mdbk,arch_ind" ExcludeLookups="name_index" />
   </CustomNonDefaultXvoClasses>

Then, you run the XvoGen tool with the -custom option, as explained in Generating the Custom
XVO Classes on page 175.




                                                                                                  177
Generating and Using Custom XVOs




178
C H A P T E R     4




Working with Database Relations



CONTENTS
 Overview: Relations and Business Objects 180

 Establishing Database Relations 199

 Removing Database Relations 210

 Using Contained Business Objects 216




                                                179
Overview: Relations and Business Objects




Overview: Relations and Business Objects
                  Using the business objects, you can access the rows related to a given row, establish new relations,
                  and remove existing relations:
                     To find the rows related to a given row, you can use the BO.FilterRelated property (as
                     explained in Specifying a Search Filter for Related Tables on page 47).
                     To establish a relation between rows, you can use the BO.RelateById method (see Establishing
                     a Relation by Object ID and Name on page 200).
                     To remove a relation between rows, you can use the BO.UnrelateById or BO.UnrelateAll
                     methods (see Removing a Relation by Object ID and Name on page 211).

                  Another convenient technique for manipulating relations is setting up a parent-child relationship
                  between the two business objects representing the related tables, as explained in these sections:
                     Understanding Parent-Child Relationships on page 180
                     Setting Up Parent-Child Relationships on page 186
                     Setting Up Hierarchies of Parent-Child Relationships on page 190
                     Controlling Queries and Updates in Child Objects on page 196

                  In addition, some types of business objects contain other business objects that are set up in
                  parent-child relationships. For details, see Using Contained Business Objects on page 216.


                  Understanding Parent-Child Relationships
                  A parent-child relationship between two business objects represents a database relation between
                  the tables for those business objects. You can set up one business object as the parent of another
                  business object to represent a specific database relation. Then, you can use the child business object
                  to access rows related to the rows in the parent business object.

                  For example, you can set up a business object for the user table (BoUser) as the parent of a
                  business object for the queue table (BoQueue) in order to represent the user_assigned2queue
                  relation. You can then use BoQueue to find the queues related to the users in BoUser.

                  You can also set up multiple child business objects under a single parent business object. A child
                  business object can have only one parent business object. The parent business object determines the
                  rows retrieved and exposed in the child business object.

                  For example, BoUser can have the child business objects BoQueue, BoWipbin, and BoCases,
                  which represent the queues, WIPbins, and cases of the users in BoUser. However, BoQueue can
                  only have one parent object: BoUser. The parent business object, BoUser, determines which rows
                  are retrieved in BoQueue.

                  The following sections explain how parent-child relationships work in more detail:
                     Querying the Parent Business Object on page 181
                     Selecting Rows in the Child Business Object on page 182
                     Selecting Rows in the Parent Business Object on page 184
                     Incrementally Retrieving Data on page 184
                     Updating the Parent Business Object on page 185


180
                                                                                   CHAPTER 4    Working with Database Relations




                Querying the Parent Business Object
                When you query the parent business object, the child business object automatically retrieves any
                rows related to the rows in the parent business object. (If a parent business object has multiple
                children, all child business objects are queried automatically.)

                Note: To prevent a child business object from automatically retrieving rows when the parent business object
                is queried, set the ChildBO.QueryMode property to cboSubmitDisabled (in Java, use
                CboConstants.cboSubmitDisabled). See Disabling Automatic Query for Child Objects on
                page 196 for details.


                In the rowset of the child business object, not all rows are exposed and accessible to your
                application or script code. When you select a row in the parent business object, the child business
                object automatically exposes any rows related to that parent row and hides the rest of the rows.

                For example, suppose you want to access all queues that a user belongs to. In the database, the
                membership of a queue is defined by the user_assigned2queue relation between the user
                table and the queue table.

                To get users and queues from the database, you create Generic business objects for the user and
                queue tables (for example, BoUser and BoQueue). To represent the user_assigned2queue
                relation between the user and queue tables, you set up BoUser as the parent of BoQueue, and you
                specify that the parent-child relationship represents the user_assigned2queue relation.

                When you query BoUser (the parent) to retrieve users from the database, BoQueue (the child)
                automatically gets queues related through the user_assigned2queue relation (see Figure 20).

Figure 20   Parent-child relationship between BoUser and BoQueue



                login_name                                                         title
                bdoe                                                               Admin Subcases
                bjensen                                                            Field Dispatches
                bstar                                                              High Priority
                                       Parent-child relationship                   Prod Lit Reqs
                    Rowset             (represents the
                                       user_assigned2queue                         Questions
              DBObjectName: user       relation)
                                                                                   Tier 2 Cases
                  Properties                                                       Urgent Cases
        BoUser (parent of BoQueue)                                                         Rowset

                                                                                DBObjectName: queue
                                             Queues related to any                ParentBO: BoUser
                                                  user in BoUser         ParentRelation: user_assigned2queue
                                                                                     TotalCount: 7

                                                                                       Properties

                                                                              BoQueue (child of BoUser)

                Not all of the rows in BoQueue are exposed and accessible. When you select a row in BoUser,
                BoQueue exposes only the rows related to that selected row. BoQueue hides the rest of the rows.



                                                                                                                           181
Overview: Relations and Business Objects




                   In the child business object, the BO.Count property is the number of rows that are currently
                   exposed. This is not necessarily the total number of rows retrieved. Use the BO.TotalCount
                   property to determine the number of rows in the rowset (including exposed and hidden rows).

                   For example, in Figure 21, if you select the row for the bjensen user in BoUser, BoQueue exposes
                   only the rows representing the queues that bjensen belongs to. BoQueue hides any rows that are
                   not related to the bjensen user. The value of the BoQueue.Count property is 3, since there are
                   three rows currently exposed in the rowset.

Figure 21    How the current row in the parent exposes and hides rows in the child

       Currently
       selected                                            Currently
            row            login_name                      selected                      title
                                                                row
                           bdoe                                                          Admin Subcases
                           bjensen                                                       Field Dispatches
                           bstar                                                         High Priority
                               Rowset                Parent-Child                        Prod Lit Reqs
                                                     Relationship                        Questions
                        DBObjectName: user                                               Tier 2 Cases
                           Position: 2
                                                                                         Urgent Cases
                              Properties
                      BoUser (parent of BoQueue)                                                 Rowset

                                                                                  DBObjectName: queue
                                                                                    ParentBO: BoUser
                                                                           ParentRelation: user_assigned2queue
                                                                                       TotalCount: 7
                                              The child business                         Count: 3
                                             object exposes only                        Position: 1
                                                  the rows of the
                                               queues related to
                                                        bjensen.                            Properties
                                                                                     BoQueue (child of BoUser)

                   Note that after you query BoUser, BoQueue contains only the rows related to the rows in the
                   BoUser rowset (not all rows from the queue table in the database).

                   Selecting Rows in the Child Business Object
                   When you navigate in the rowset for the child business object, the BO.Position property and
                   BO.MoveXXX methods (for example, BO.MoveNext) only apply to the exposed rows. Hidden rows
                   have no position. The business object skips these rows when you use the BO.MoveXXX methods.

                   For example, if you select the row for the bjensen user in BoUser (see Figure 22), the exposed
                   rows for the High Priority, Tier 2 Cases, and Urgent Cases queues correspond to positions 1, 2, and
                   3 in the rowset. Calling the BoQueue.MoveFirst method sets the currently selected row to the
                   High Priority queue. Calling the BoQueue.MoveNext method sets the currently selected row to
                   the Tier 2 Cases queue. These methods skip hidden queues like the Admin Subcases queue.

                   If the current row in the parent business object is not related to any rows in the child business
                   object, all rows in the child business object are hidden. In this case, the rowset of the child business
                   object is like an empty rowset: the BO.Count and BO.Position properties are set to 0, and the
                   BO.BOF and BO.EOF properties are set to True. Calling the BO.MoveXXX methods and setting the
                   BO.Position property have no effect on the current row, since there are no rows to select.


182
                                                                                             CHAPTER 4      Working with Database Relations




Figure 22   How the BO.MoveXXX methods and BO.Position work with exposed rows

Currently
selected                                                                                                                   Position: 1
     row            login_name                                                  title                                      Position: 2
                                              Parent-child                                                                 Position: 3
                    bdoe                      relationship                      Admin Subcases
                    bjensen                                                     Field Dispatches
                    bstar                                                       High Priority
                        Rowset                                                  Prod Lit Reqs
                                                                                Questions
                DBObjectName: user
                   Position: 2                                                  Tier 2 Cases
                       Properties                                               Urgent Cases                          The
                                                                                    Rowset                            BoQueue.Move
            BoUser (parent of BoQueue)                                                                                methods and
                                                                           DBObjectName: queue                        BoQueue.Positio
                                 Currently selected row                      ParentBO: BoUser                         n property skip
                                           after you call           ParentRelation: user_assigned2queue               any hidden rows.
                                  BoQueue.MoveNext                              TotalCount: 7
                                                                                  Count: 3
                                                                                 Position: 2
                      The newly selected row
                    has the position 2. Hidden
                       rows have no position.                                       Properties
                                                                            BoQueue (child of BoUser)

                For example, suppose you select the row for the bdoe user in BoUser (see Figure 23), and this user
                is not related to any queues. No rows in the child business object are exposed. Calling the
                BoQueue.MoveNext method has no effect because there are no exposed rows to move to.
                BoQueue.Position is always 0.

Figure 23   How the BO.MoveXXX methods and BO.Position work with a rowset of hidden rows

        Currently
        selected
             row              login_name                                                         title
                              bdoe                                                               Admin Subcases
                              bjensen                                                            Field Dispatches
                                                             Parent-child
                              bstar                          relationship                        High Priority
                                  Rowset                                                         Prod Lit Reqs
                                                                                                 Questions
                           DBObjectName: user                                                    Tier 2 Cases
                              Position: 1
                                                                                                 Urgent Cases
                                 Properties
                       BoUser (parent of BoQueue)                                                         Rowset

                                                                                               DBObjectName: queue
                                                                                                 ParentBO: BoUser
                                       The BoQueue.Move methods and                     ParentRelation: user_assigned2queue
                                      BoQueue.Position property have no                             TotalCount: 7
                                       effect on the rowset, since none of                            Count: 0
                                                    the rows are exposed.                            Position: 0


                                                                                                         Properties
                                                                                             BoQueue (child of BoUser)



                                                                                                                                         183
Overview: Relations and Business Objects




                  Selecting Rows in the Parent Business Object
                  If you select a different row in the parent business object, the child business object exposes the rows
                  that are related to that selected row and hides the other rows. The child business object
                  automatically selects the first row (if any rows are exposed). The BO.Count property of the child
                  business object changes to reflect the number of rows now exposed.

                  For example, in Figure 24, the currently selected row in BoUser has changed from bjensen to
                  bstar. As a result, BoQueue hides the rows related to bjensen and exposes the rows related to
                  bstar. The BoQueue.Count property is now 4 (since 4 rows are now exposed), and the currently
                  selected row in BoQueue is the first row (regardless of which row was previously selected).

Figure 24    The effect of changing the position in the parent rowset
         Currently
         selected                                             Currently
              row            login_name                       selected               title
                                                                   row
                             bdoe                                                    Admin Subcases
                             bjensen                                                 Field Dispatches
                             bstar                                                   High Priority
                                 Rowset                 Parent-Child                 Prod Lit Reqs
                                                        Relationship                 Questions
                          DBObjectName: user                                         Tier 2 Cases
                             Position: 3
                                                                                     Urgent Cases
                                Properties
                      BoUser (parent of BoQueue)                                              Rowset

                                                                                   DBObjectName: queue
                                                                                     ParentBO: BoUser
                                                                            ParentRelation: user_assigned2queue
                                                                                        TotalCount: 7
                            The child business                                            Count: 4
                           object exposes only                                           Position: 1
                                the rows of the
                             queues related to
                                         bstar.
                                                                                             Properties
                                                                                  BoQueue (child of BoUser)


                  Incrementally Retrieving Data
                  Paging Search Results on page 65 explains how to retrieve data from selected rows at a time (instead
                  of data for all rows in the rowset). To retrieve data incrementally, set the BO.QueryMode property
                  to cboSubmitRequery (in Java, use CboConstants.cboSubmitRequery) and set the
                  BO.RequeryFirst and BO.RequeryLast properties to the positions of the first and last row
                  that you want to get data for.

                  When you use this mode to query a parent business object, the child business object retrieves data
                  only for those parent business object rows that contain data.




184
                                                                 CHAPTER 4   Working with Database Relations




To set up the child business object to retrieve data incrementally, do the following:
1. Set the ChildBO.QueryMode property to cboSubmitQueryIDsOnly (in Java, use
   CboConstants.cboSubmitQueryIDsOnly) to set up the business object to retrieve object
   IDs only (no other data).
2. Query the database (for example, by calling the ParentBO.Query method).
3. Set up the child business object to query for data from a subset of rows by setting the
   ChildBO.QueryMode property to cboSubmitRequery.
4. Set the ChildBO.RequeryFirst and ChildBO.RequeryLast properties to the positions of
   the first and last row that you want to retrieve data from. (1 indicates the first row in the
   rowset.)
5. Query the child business object to get the data for those rows (for example, by calling the
   ChildBO.Query method).
6. To query for other subsets of rows, change the values of the ChildBO.RequeryFirst and
   ChildBO.RequeryLast properties, and query the child business object again.

If you want to retrieve data incrementally for both the parent and child business objects, set the
ChildBO.QueryMode property to cboSubmitBlocked to prevent unintentional requeries when
querying the parent business object.

Updating the Parent Business Object
When you update the parent business object, any changes in any of the child business objects are
also committed to the database. All changes are grouped together in to a single transaction. If any
change in the parent or child business objects fails, none of the changes are committed.

Note: To prevent a child business object from automatically committing changes when the parent business
object is updated, set the ChildBO.UpdateMode property to cboSubmitDisabled or
cboSubmitBlocked (in Java, use CboConstants.cboSubmitDisabled and
CboConstants.cboSubmitBlocked). See Disabling Automatic Updates for Child Objects on
page 198 for details.




                                                                                                        185
Overview: Relations and Business Objects




                  Setting Up Parent-Child Relationships
                  To establish a parent-child relationship between two business objects, set the following properties
                  on the child business object:
                     ChildBO.ParentName (a design-time property) or ParentBO (a run-time property)
                     If you want to set up the parent-child relationship during design time (for example, if you are
                     working with dropped controls in Visual Basic), set the ParentName property to the name of
                     the parent business object.
                     If you want to set up the parent-child relationship during run-time (for example in a JSP page),
                     set the ParentBO property to the object reference for the parent business object.

                     Caution: You can only add rows to a child object after you have set the ParentBO property. If you add
                     rows to a child object and then set the ParentBO property, you get an exception.

                     ChildBO.ParentRelation
                     Set this property to the name of the database relation that begins with the parent business object
                     and that ends with the child business object.
                     For example, if a business object for the user table is the parent of a business object for the
                     queue table, set this property to user_assigned2queue. On the other hand, if the business
                     object for the queue table is the parent of the business object for the user table, set this
                     property to queue2user.
                     You can set this property to any kind of database relation (many-to-many, or MTM relations;
                     one-to-many, or OTM relations; one-to-one, or OTO relations), even exclusive relations. For
                     more information on exclusive relations, see the Data Dictionary Guide.

                  When setting up parent-child relationships, keep the following guidelines in mind:
                     When setting up parent-child relationships, note that a child business object can have only one
                     parent business object. Each parent business object can have multiple child business objects.
                     If you are representing a MTM relation that is implemented with an mtm_ table, you do not
                     need to set up a separate business object for the mtm_ table.
                     Some types of business objects contain other business objects that are already set up in
                     parent-child relationships. For details, see Using Contained Business Objects on page 216.

                  The following sections provide examples of setting up parent-child relationships:
                     Setting Up Relationships in Java on page 187
                     Setting Up Relationships in JavaScript on page 187
                     Setting Up Relationships in Visual Basic on page 187
                     Example of Using a Parent-Child Relationship on page 188




186
                                                               CHAPTER 4   Working with Database Relations




Setting Up Relationships in Java
The following example illustrates how to set up a parent-child relationship between boUser (a
Generic business object for the user table) and boQueue (a Generic business object for the
queue table). The relationship represents the user_assigned2queue relation.
// Java Example
Generic boUser, boQueue;
...
boQueue.setParentRelation("user_assigned2queue");
boQueue.setParentBO(boUser);

Setting Up Relationships in JavaScript
The following example illustrates how to set up a parent-child relationship between BoUser (a
Generic business object for the user table) and BoQueue (a Generic business object for the
queue table). The relationship represents the user_assigned2queue relation.
// JavaScript Example
var BoUser, BoQueue;
...
BoQueue.ParentRelation = "user_assigned2queue";
BoQueue.ParentBO = BoUser;

Setting Up Relationships in Visual Basic
In Visual Basic, you can set up the parent-child relationship during design-time or during run-time.

The following example shows how to set up a parent-child relationship between BoUser (a
Generic business object for the user table) and BoQueue (a Generic business object for the
queue table). The relationship represents the user_assigned2queue relation.
'Visual Basic Example
Dim BoUser, BoQueue
...
BoQueue.ParentRelation = "user_assigned2queue"
Set BoQueue.ParentBO = BoUser

Note that in Visual Basic, you need to specify "Set", since you are setting the property to an object.

To set up the parent-child relationship during design-time, drop the business objects on the form,
and use the properties sheet to set the ParentName and ParentRelation properties of the child
business object.

For example, suppose that you drop the following Generic business objects on a form:
   Generic1 (a business object for the user table)
   Generic2 (a business object for the queue table)

To set up Generic1 as the parent of Generic2, use the properties sheet for Generic2 (the child
business object) to set the following properties:
   ParentRelation property: user_assigned2queue
   ParentName property: Generic1




                                                                                                      187
Overview: Relations and Business Objects




                  Example of Using a Parent-Child Relationship
                  The following example generates a list of all users and the queues that they belong to. BoUser and
                  BoQueue are Generic business objects for the user and queue tables (respectively). The example
                  sets up these objects in a parent-child relationship in order to get a list of the queues that each user
                  belongs to.

                  After querying the business objects, the example iterates through each row in BoUser. For each
                  row, the example iterates through the exposed rows in BoQueue to get the titles of the queues that
                  the given user belongs to.
                     In Java (JSP):
                     ...
                     <%@ page import="com.clarify.cbo.*" %>
                     ...
                     <% int nUsers = 0;
                     int nQueues = 0;
                     // Create a Generic business object to find all users.
                     Generic BoUser = clfyBoContext.createGenericBO("user");
                     BoUser.setDataFields("*");
                     BoUser.setSortOrder("login_name");
                     // Create a child business object to find queues for those users.
                     Generic BoQueue = clfyBoContext.createGenericBO("queue");
                     BoQueue.setDataFields("*");
                     BoQueue.setSortOrder("title");
                     BoQueue.setParentRelation("user_assigned2queue");
                     BoQueue.setParentBO(BoUser);
                     // Query the business objects.
                     BoUser.query( ); %>
                     <HTML>
                     <HEAD>
                     <TITLE>Users and Their Queues</TITLE>
                     </HEAD>
                     <BODY>
                     <!-- Display the number of users found. -->
                     <P>Users Found: <%=BoUser.getCount()%></P>
                     <TABLE BORDER=1>
                        <TR VALIGN=BASELINE>
                           <TH>User</TH>
                           <TH>Queues</TH>
                        </TR>
                     <% // Iterate through each row in the parent business object.
                     // Get the login name of each user.
                     for (nUsers = 1; nUsers <= BoUser.getCount(); nUsers++) {
                        %>
                        <TR VALIGN=BASELINE>
                           <TD><%=BoUser.getValue("login_name")%>&nbsp;</TD>
                           <TD>
                           <% // Iterate through each row in the child business object.
                           // Get the title of each queue.
                           for (nQueues = 1; nQueues <= BoQueue.getCount(); nQueues++) {
                              %>
                              <%=BoQueue.getValue("title")%><BR>
                              <%
                              // Move to the next row in the child business object.
                              BoQueue.moveNext();
                           } %>


188
                                                CHAPTER 4   Working with Database Relations




      &nbsp;</TD>
   </TR>
   <% // Move to the next row in the parent business object.
   BoUser.moveNext();
} %>
</TABLE> </BODY> </HTML>
In JavaScript (ASP):
<%@ Language=JavaScript %>
<%Response.Buffer = true%>
...
<% var BoUser, BoQueue, nUsers, nQueues;
// Create a Generic business object to find all users.
BoUser = ClfyForm.CreateGenericBO("user");
BoUser.DataFields = "*";
BoUser.SortOrder = "login_name";
// Create a child business object to find queues for those users.
BoQueue = ClfyForm.CreateGenericBO("queue");
BoQueue.DataFields = "*";
BoQueue.SortOrder = "title";
BoQueue.ParentBO = BoUser;
BoQueue.ParentRelation = "user_assigned2queue";
// Query the business objects.
BoUser.query( ); %>
<HTML>
<HEAD>
<TITLE>Users and Their Queues</TITLE>
</HEAD>
<BODY>
<!-- Display the number of users found. -->
<P>Users Found: <%=BoUser.Count%></P>
<TABLE BORDER=1>
   <TR VALIGN=BASELINE>
      <TH>User</TH>
      <TH>Queues</TH>
   </TR>
<% // Iterate through each row in the parent business object.
// Get the login name of each user.
for (nUsers = 1; nUsers <= BoUser.Count; nUsers++) {
   %>
   <TR VALIGN=BASELINE>
      <TD><%=BoUser("login_name")%>&nbsp;</TD>
      <TD>
      <% // Iterate through each row in the child business object.
      // Get the title of each queue.
      for (nQueues = 1; nQueues <= BoQueue.Count; nQueues++) {
         %>
         <%=BoQueue("title")%><BR>
         <% // Move to the next row in the child business object.
         BoQueue.MoveNext();
      } %>
      &nbsp;</TD>
   </TR>
   <% // Move to the next row in the parent business object.
   BoUser.MoveNext();
   } %>
</TABLE> </BODY> </HTML>


                                                                                       189
Overview: Relations and Business Objects




                   Setting Up Hierarchies of Parent-Child Relationships
                   In some situations, you may need to traverse relations across multiple tables. In these types of
                   situations, you can set up multiple business objects in parent-child hierarchies.

                   For example, suppose you want to get the contacts associated with a given site. To get this
                   information, you need to traverse relations from the site table through the contact_role table
                   to the contact table (see Figure 25).

Figure 25    Traversing relations across several tables


  name                                             role_name                                   first_name     last_name
  PicoElectronics                                  default                                     Barbara        Jensen
                           site2contact_role                            contact_role2contact
  Terabytes Inc                                    primary                                     Veronica       Hicks
      site table                                   secondary                                   Jane           Smith
                                                   contact_role table                          Amelia         Graywords
                                                                                               Rene           Bruce
                                                                                                      contact table

                   In your application or script code, you need to set up business objects for these tables in a
                   parent-child hierarchy. To set up the hierarchy, you need to do the following:
                      Create Generic business objects for the site, contact_role, and contact tables (for
                      example, BoSite, BoContactRole, and BoContact).
                      Set up BoSite as the parent of BoContactRole, using the site2contact_role relation.
                      Set up BoContactRole as the parent of BoContact, using the contact_role2contact
                      relation.

                   When you select a site in BoSite, BoContactRole exposes the contact roles related to that site,
                   and BoContact exposes the contact related to the currently selected contact role. See Figure 26 for
                   a diagram of this hierarchy.




190
                                                                                        CHAPTER 4   Working with Database Relations




Figure 26   Setting up multiple levels of parent-child relationships



                            Currently selected row           name
                                                             PicoElectronics
                                                             Terabytes Inc
                                                                 Rowset

                                                          DBObjectName: site
                                                             Position: 2
                                                                Properties
                                                     BoSite (parent of BoContactRole)
                                                                                                        Parent-child
                                                                                                        relationship


                    Currently selected row
                                                             role_name
                                                             default
                                                             primary
                                                             secondary
                                                                 Rowset
       The child business object exposes             DBObjectName: contact_role
        only the rows associated with the                  ParentBO: BoSite
                           selected site.           ParentRelation: site2contact_role
                                                             TotalCount: 3
                                                                Count: 2
                                                              Position: 1

                                                                Properties
                                            BoContactRole (child of BoSite, parent of BoContact)



                                                                                                         Parent-child
                                                                                                         relationship
                 Currently selected row                  first_name    last_name
                                                         Barbara       Jensen
                                                         Veronica      Hicks
                                                         Jane          Smith
                                                         Amelia        Graywords
                                                         Rene          Bruce
              The child business object                             Rowset
                 exposes only the rows
            associated with the selected                 DBObjectName: contact
                            contact role.              ParentBO: BoContactRole
                                                   ParentRelation: contact_role2contact
                                                              TotalCount: 5
                                                                 Count: 1
                                                                Position: 1


                                                                  Properties
                                                    BoContact (child of BoContactRole)




                                                                                                                               191
Overview: Relations and Business Objects




                  When you query a business object in the hierarchy, all business objects under that object in the
                  hierarchy are queried by default, including business objects that are more than one level under that
                  object. For example, in Figure 26, when you query BoSite (the topmost parent business object),
                  BoContactRole and BoContact are automatically queried, even if BoContact is more than one
                  level beneath BoSite.

                  Similarly, when you update a business object in the hierarchy, all business objects under that object
                  in the hierarchy are also updated. All changes in these business objects are grouped in to a single
                  transaction. (If any change cannot be committed to the database, none of the changes are
                  committed to the database.)

                  You can prevent selected business objects or entire subtrees of business objects from getting
                  queried and updated automatically. For details, see Controlling Queries and Updates in Child Objects
                  on page 196.

                  The following section of code is an example of setting up a hierarchy of parent-child business
                  objects. This example uses the hierarchy of parent-child business objects to display a list of the sites
                  in the database, along with the contact roles associated with each site and the contact associated
                  with each contact role.
                     In Java (JSP):
                     ...
                     <%@ page import="com.clarify.cbo.*" %>
                     ...
                     <%
                     int nSites = 0, nRoles = 0, nContacts = 0, nRows = 0;
                     // Create a Generic business object to find all sites.
                     Generic BoSite = clfyBoContext.createGenericBO("site");
                     BoSite.setDataFields("*");
                     BoSite.setSortOrder("name");

                     // Create a child business object to find contact roles for sites.
                     Generic BoContactRole = clfyBoContext.createGenericBO("contact_role");
                     BoContactRole.setDataFields("*");
                     BoContactRole.setSortOrder("role_name");
                     BoContactRole.setParentRelation("site2contact_role");
                     BoContactRole.setParentBO(BoSite);

                     // Create a child business object to find contacts for those roles.
                     Generic BoContact = clfyBoContext.createGenericBO("contact");
                     BoContact.setDataFields("*");
                     BoContact.setSortOrder("last_name, first_name");
                     BoContact.setParentRelation("contact_role2contact");
                     BoContact.setParentBO(BoContactRole);

                     // Query the business objects. You need to query only the business
                     // object at the top of the hierarchy (BoSite). BoContactRole and
                     // BoContact are queried automatically when you query BoSite.
                     BoSite.query();
                     %>
                     <HTML>
                     <HEAD>
                     <TITLE>Sites and Their Contacts</TITLE>
                     </HEAD>
                     <BODY>




192
                                                CHAPTER 4   Working with Database Relations




<!-- Display the number of sites found. -->
<P>Sites Found: <%=BoSite.getCount()%></P>
<TABLE BORDER=1>
   <TR VALIGN=BASELINE>
      <TH>Sites</TH>
      <TH>Contact Roles</TH>
      <TH>Contacts</TH>
   </TR>
   <%
   // Iterate through each row in the parent business object.
   // Get the name of each site.
   for (nSites = 1; nSites <= BoSite.getCount(); nSites++) {
   %>
      <TR VALIGN=BASELINE>
      <%
      if (BoContactRole.getCount() > 1) {
         nRows = BoContactRole.getCount();
      } else {
         nRows = 1;
      }
      %>
      <TD ROWSPAN=<%=nRows%>><%=BoSite.Value("name")%>&nbsp;</TD>
      <%
      // Iterate through each row in the child business object.
      // Get the name of each contact role.
      for (nRoles = 1; nRoles <= BoContactRole.getCount(); nRoles++){
         if (nRoles > 1) {
            %>
            </TR>
            <TR VALIGN=BASELINE>
            <%
         }
         %>
         <TD><%=BoContactRole.getValue("role_name")%></TD>
         <TD>
         <%
         // Iterate through each row in the next level of the child
         // business object.
         // Get the first and last name of each contact.
         for (nContacts = 1; nContacts <= BoContact.getCount();
            nContacts++) {
            %>
            <%=BoContact.getValue("first_name")%>
            &nbsp;<%=BoContact.getValue("last_name")%><BR>
            <%
            // Move to the next row in the child business object.
            BoContact.moveNext();
         }
         %>
         </TD>
         <%
         // Move to the next row in the child business object.
         BoContactRole.moveNext();
      }
      // Indicate if no contacts are associated with the site.
      if (BoContactRole.getCount() == 0) {
         %>


                                                                                       193
Overview: Relations and Business Objects




                                 <TD COLSPAN=2>No contacts associated with this site</TD>
                                 <%
                            }
                            %>
                         </TR>
                         <%
                         // Move to the next row in the parent business object.
                         BoSite.moveNext();
                     }
                     %>
                     </TABLE>
                     </BODY>
                     </HTML>
                     In JavaScript (ASP):
                     <%@ Language=JavaScript %>
                     <%Response.Buffer = true%>
                     ...
                     <%
                     var BoSite, BoContactRole, BoContact, nSites, nRoles, nContacts,
                        nRows;

                     // Create a Generic business object to find all sites.
                     BoSite = ClfyForm.CreateGenericBO("site");
                     BoSite.DataFields = "*";
                     BoSite.SortOrder = "name";

                     // Create a child business object to find contact roles for sites.
                     BoContactRole = ClfyForm.CreateGenericBO("contact_role");
                     BoContactRole.DataFields = "*";
                     BoContactRole.SortOrder = "role_name";
                     BoContactRole.ParentRelation = "site2contact_role";
                     BoContactRole.ParentBO = BoSite;

                     // Create a child business object to find contacts for those roles.
                     BoContact = ClfyForm.CreateGenericBO("contact");
                     BoContact.DataFields = "*";
                     BoContact.SortOrder = "last_name, first_name";
                     BoContact.ParentRelation = "contact_role2contact";
                     BoContact.ParentBO = BoContactRole;

                     // Query the business objects. You need to query only the business
                     // object at the top of the hierarchy (BoSite). BoContactRole and
                     // BoContact are queried automatically when you query BoSite.
                     BoSite.Query();
                     %>
                     <HTML>
                     <HEAD>
                     <TITLE>Sites and Their Contacts</TITLE>
                     </HEAD>
                     <BODY>

                     <!-- Display the number of sites found. -->
                     <P>Sites Found: <%=BoSite.Count%></P>
                     <TABLE BORDER=1>
                        <TR VALIGN=BASELINE>
                           <TH>Sites</TH>


194
                                                CHAPTER 4   Working with Database Relations




     <TH>Contact Roles</TH>
     <TH>Contacts</TH>
</TR>
<%
// Iterate through each row in the parent business object.
// Get the name of each site.
for (nSites = 1; nSites <= BoSite.Count; nSites++) {
   %>
   <TR VALIGN=BASELINE>
   <%
   // Get the number of table rows of contact roles that
   // you need to span for a given site table row.
   if (BoContactRole.Count > 1) {
      nRows = BoContactRole.Count;
   } else {
      nRows = 1;
   }
   %>
   <TD ROWSPAN=<%=nRows%>><%=BoSite("name")%>&nbsp;</TD>
   <%
   // Iterate through each row in the child business object.
   // Get the name of each contact role.
   for (nRoles = 1; nRoles <= BoContactRole.Count; nRoles++) {
      // Start a new table row if there’s more than one contact row.
      if (nRoles > 1) {
         %>
         </TR>
         <TR VALIGN=BASELINE>
         <%
      }
      %>
      <TD><%=BoContactRole("role_name")%></TD>
      <TD>
      <%
      // Iterate through each row in the next child business object.
      // Get the first and last name of each contact.
      for (nContacts = 1; nContacts <= BoContact.Count; nContacts++){
         %>
         <%=BoContact("first_name")%>&nbsp;
         <%=BoContact("last_name")%><BR>
         <%
         // Move to the next row in the child business object.
         BoContact.MoveNext()
      }
      %>
      </TD>
      <%
      // Move to the next row in the child business object.
      BoContactRole.MoveNext();
   }
   // Indicate if no contacts are associated with the site.
   if (BoContactRole.Count == 0) {
      %>
      <TD COLSPAN=2>No contacts associated with this site</TD>
      <%
   }
   %>


                                                                                       195
Overview: Relations and Business Objects




                         </TR>
                         <%
                         // Move to the next row in the parent business object.
                         BoSite.MoveNext();
                     }
                     %>
                     </TABLE>
                     </BODY>
                     </HTML>


                  Controlling Queries and Updates in Child Objects
                  By default, when you query or update the parent business object, the child business objects are
                  queried and updated automatically.

                  In some situations, you may want to prevent the query or update from happening automatically.
                  The following sections describe this in more detail:
                     Disabling Automatic Query for Child Objects on page 196
                     Disabling Automatic Updates for Child Objects on page 198

                  Disabling Automatic Query for Child Objects
                  By default, when you query a parent business object, any child business objects are also queried
                  automatically. In certain situations, you may want to prevent child business objects from getting
                  queried automatically.

                  For example, suppose you want to use parent and child business objects to add a log to a workflow
                  item. You set up a business object for a log (such as an email log or an activity entry log) as the child
                  of the business object for the workflow item. Because you’re using the child business object to add
                  a related log, you don’t need to automatically query the child business object for all existing logs
                  related to the workflow item. (Querying the child business object might retrieve a large number of
                  logs.)

                  If you want to prevent a child business object from being queried with the parent business object,
                  set the BO.QueryMode property of the child business object to cboSubmitDisabled (in Java, use
                  CboConstants.cboSubmitDisabled).




196
                                                            CHAPTER 4   Working with Database Relations




The following example demonstrates how to disable the automatic query of a child business object.
The example sets up a business object for the user table (BoUser) as the parent of a business
object for the act_entry table (BoActEntry). To prevent BoActEntry from querying for related
activity log entries, the example sets BOActEntry.QueryMode to cboSubmitDisabled.
   In Java (JSP):
   <%@ page import="com.clarify.cbo.*" %>
   ...
   <%
   // Create a Generic business object to find all users.
   Generic BoUser = clfyBoContext.createGenericBO("user");
   BoUser.setDataFields("*");
   BoUser.setSortOrder("login_name");
   // Create a child business object for the activity log entries.
   ActEntry BoActEntry =
      (ActEntry) clfyBoContext.createBO("com.clarify.cbo.ActEntry");
   BoActEntry.setDataFields("*");
   BoActEntry.setSortOrder("entry_time");
   BoActEntry.setParentRelation("originator2act_entry");
   BoActEntry.setParentBO(BoUser);
   // Prevent activity log entries for getting queried when
   // users are queried.
   BoActEntry.setQueryMode(CboConstants.cboSubmitDisabled);
   ...
   // BoActEntry is not included in this query.
   BoUser.query();
   ... %>
   In JavaScript (ASP):
   ...
   var BoUser, BoActEntry;
   // For JavaScript in the Clarify ASP pages, you need to define the
   // cboSubmitDisabled constant.
   var cboSubmitDisabled = 1;
   ...
   // Create a Generic business object to find all users.
   BoUser = ClfyForm.CreateGenericBO("user");
   BoUser.DataFields = "*";
   BoUser.SortOrder = "login_name";

   // Create a child business object for the activity log entries.
   BoActEntry = ClfyForm.CreateBO("Clarify.CBO.ActEntry");
   BoActEntry.DataFields = "*";
   BoActEntry.SortOrder = "entry_time";
   BoActEntry.ParentRelation = "originator2act_entry";
   BoActEntry.ParentBO = BoUser;

   // Prevent activity log entries for getting queried when
   // users are queried.
   BoActEntry.QueryMode = cboSubmitDisabled;
   ...
   // BoActEntry is not included in this query.
   BoUser.Query();
   ...




                                                                                                   197
Overview: Relations and Business Objects




                  Disabling Automatic Updates for Child Objects
                  By default, when you update a parent business object, any child business objects are also updated
                  automatically. In certain situations, you may want to prevent child business objects from getting
                  updated automatically.

                  To prevent a child business object from being updated with the parent business object, set the
                  BO.UpdateMode property of the child business object to cboSubmitDisabled (in Java, use
                  CboConstants.cboSubmitDisabled).

                  In situations where you have a multiple-level hierarchy of parent-child relationships (see Setting
                  Up Hierarchies of Parent-Child Relationships on page 190 for more information), setting the
                  BO.UpdateMode property of the child business object to cboSubmitDisabled does not disable
                  automatic updates for any of the business objects under that child business object.

                  For example, in Figure 27, BoGeneric1 is the parent of BoGeneric2, and BoGeneric2 is the
                  parent of BoGeneric3 and BoGeneric4. If BoGeneric2.UpdateMode is
                  cboSubmitDisabled, calling the BoGeneric1.Update method still commits any changes in
                  BoGeneric3 and BoGeneric4.

Figure 27    How the cboSubmitDisabled mode affects updates
                  UpdateMode:
                cboSubmitEnabled           BoGeneric1




                  UpdateMode:              BoGeneric2                      No update performed
               cboSubmitDisabled                                           when you call
                                                                           BoGeneric1.Update




                         BoGeneric3                      BoGeneric4
                         UpdateMode:                      UpdateMode:
                       cboSubmitEnabled                 cboSubmitEnabled

             If UpdateMode = cboSubmitDisabled for BoGeneric2,               Legend:
             changes in BoGeneric3 and BoGeneric4 are still committed
             when you call BoGeneric1.Update.                                          = update performed


                                                                                        = no update performed

                  If you want to disable automatic updates below a certain business object in the hierarchy, you can
                  set the BO.UpdateMode property of that business object to cboSubmitBlocked (in Java, use
                  CboConstants.cboSubmitBlocked). For example, in Figure 28, BoGeneric2.UpdateMode is
                  cboSubmitDisabled. When you call the BoGeneric1.Update method, none of the changes in
                  BoGeneric2, BoGeneric3, or BoGeneric4 are committed to the database.




198
                                                                                CHAPTER 4    Working with Database Relations




Figure 28   How the cboSubmitBlocked mode affects updates

                 UpdateMode:            BoGeneric1
               cboSubmitEnabled




                 UpdateMode:            BoGeneric2                         No update performed
              cboSubmitBlocked                                             when you call
                                                                           BoGeneric1.Update




                       BoGeneric3                      BoGeneric4

                       UpdateMode:                     UpdateMode:
                     cboSubmitEnabled                cboSubmitEnabled

            If UpdateMode = cboSubmitBlocked for BoGeneric2, none of the      Legend:
            changes in BoGeneric2, BoGeneric3, and BoGeneric4 are
            committed when you call BoGeneric1.Update.                                      = update performed


                                                                                            = no update performed




Establishing Database Relations
                Suppose you want to establish a database relation between row A in the database table X and row
                B in the database table Y. If you have a business object for table X in which row A is the currently
                selected row, you can do one of the following:
                   You can call the BO.RelateById method, specifying the database relation name and the object
                   ID of row B. If you use this method, you do not need to set up any parent-child relationships.
                   For details, see Establishing a Relation by Object ID and Name on page 200.
                   You can set up a child business object for table Y and copy row B to this child business object.
                   For details, see Establishing a Relation Through Parent-Child Objects on page 201.
                   Note that some business objects already have child business objects that are exposed as
                   properties. If you are working with these types of business objects, you can use these properties
                   to access the child business objects. See Using Contained Business Objects on page 216 for details.




                                                                                                                        199
Establishing Database Relations




                  Establishing a Relation by Object ID and Name
                  To establish a relation between two rows, you can use the BO.RelateById method. You need to
                  pass in the object ID of the related row and the database relation name as arguments to this
                  method:
                  BO.RelateById(objid, relation_name)

                  Note that this change is not committed to the database until you call the BO.Update method (or
                  one of the other update methods described in Committing Changes to the Database on page 83).

                  For example, the following section of code establishes the user_assigned2queue relation
                  between the currently selected row in BoUser (a business object for the user table) and the row in
                  the queue table with the object ID 268435457:
                      In Java:
                      import com.clarify.cbo.*;
                      ...
                      Generic BoUser = null;
                      String strObjid = "268435457";
                      ...
                      // Establish a relation between the currently selected user
                      // and the queue with the specified object ID.
                      BoUser.relateById(strObjid, "user_assigned2queue");
                      // Commit the change to the database.
                      BoUser.update();
                      In JavaScript:
                      var strObjid = "268435457";
                      ...
                      // Establish a relation between the currently selected user
                      // and the queue with the specified object ID.
                      BoUser.RelateById(strObjid, "user_assigned2queue");
                      // Commit the change to the database.
                      BoUser.Update();

                  In this example, the row in the queue table does not need to be in any business object in memory.

                  Note that this method does not perform any application-level constraint checking. For example,
                  suppose that you use the BO.RelateById method to associate a contract with a case. Although
                  this contract should also be related to the site reporting the case, this method does not verify that
                  the contract is related to the site.

                  The BO.RelateById method is common to all business objects. This method does not include any
                  logic for checking constraints particular to a specific part of the data model.

                  In your script code that uses this method, you should verify that the relation is allowed before you
                  call the BO.RelateById method.




200
                                                                                          CHAPTER 4   Working with Database Relations




                 Establishing a Relation Through Parent-Child Objects
                 If two business objects are set up in a parent-child relationship, you can establish a relation to the
                 currently selected row in the parent business object by adding the row that you want related to the
                 child business object.

                 For example, suppose you set up a Generic business object for the user table (BoUser) and a
                 Generic business object for the queue table (BoQueue). You set up BoUser as the parent of
                 BoQueue, using the user_assigned2queue relation.

                 If you want to establish a relation between the queue named New Cases and the user bjensen,
                 you can select bjensen in BoUser (which exposes the queues related to that user in BoQueue)
                 and add the New Cases queue to the rowset in BoQueue (see Figure 29).

Figure 29    Using the parent-child relationships to establish a database relation

                              Parent-child
                              relationship
   login_name                                     objid         title                description
   bdoe                                           268435457     Admin Subcases Queue for administrative subcases
   bjensen                                        268435458     Field Dispatches Queue for cases with field eng. dispatches
   bstar                                          268435459     High Priority        Queue for high priority cases
       Rowset                 new relation        268435460     Prod Lit Reqs        Queue for product literature requests
 BoUser (Generic business                         268435461     Questions            Queue for cases that are questions
 object for the user table)                       268435462     Tier 2 Cases         Queue for tier 2 cases
                                                  268435463     Urgent Cases         Queue for urgent priority cases
                                                  268435464     New Cases            Queue for new cases
                Newly added row                                                      Rowset

                                                                        ParentRelation: user_assigned2queue
                                                                                 ParentBO: BoUser

 To associate the queue named New                                                    Properties
 Cases with the user bjensen, select the
 row for the bjensen user in BoUser and                             BoQueue (Generic business object for
 copy the row for the New Cases queue to                                     the queue table)
 BoQueue.


                 To add a row to a child business object, you can use one of the following methods:
                     If you know the object ID of the row that you want to relate, use the BO.AddById or
                     BO.ReplaceById methods of the child business object. (See Adding or Replacing a Child by
                     Object ID on page 202 for details.)
                     If you want to copy the rows from a separate business object to the child business object, use the
                     BO.Add or BO.Replace methods of the child business object. (See Adding or Replacing a Child
                     by Copying on page 205 for details.)
                     If you want to create a new row in the database table and relate the row to the currently selected
                     row in the parent business object, use the BO.AddNew method of the child business object. (See
                     Adding a New Empty Row on page 209 for details.)




                                                                                                                                 201
Establishing Database Relations




                  Note that these methods also apply to business objects that contain other business objects in
                  parent-child relationships. For details, see Using Contained Business Objects on page 216.

                  Caution: You must set the ParentBO property before you add rows to a child object. Otherwise, you get an
                  error when you set the ParentBO property.


                  Adding or Replacing a Child by Object ID
                  You can use a parent-child relationship to establish a relation by selecting a row in the parent
                  business object and adding a row to the child business object. This sets up a relation between the
                  selected row in the parent business object and the new row in the child business object.

                  If you know the object ID of the row that you want to add to the child business object, use the
                  BO.AddById method of the child business object to add the row to the child.

                  Note: You should use BO.ReplaceById instead of BO.AddById if the parent business object’s relation
                  to the current business object is an MTO relation, an OTO relation, or an exclusive relation. In this type of
                  situation, the child business object must have at most one exposed row. If you call BO.AddById and the
                  rowset already contains a row, an error is thrown. If you call BO.ReplaceById instead, any existing row
                  is replaced. If the rowset contains no rows, a new row is added.


                  After adding the row, you need to call the child business object’s BO.Update method to commit
                  the relation to the database. (If you have changes in other child business objects, you can call the
                  parent business object’s BO.Update method to commit all changes in a single transaction.)

                  For example, suppose you want to associate the New Cases queue with the user bjensen. You set
                  up a Generic business object for the user table (BoUser) as the parent of a Generic business
                  object for the queue table (BoQueue). If you know the object ID of the New Cases queue, you can
                  use the following statements to relate the queue to the user:
                      In Java:
                      import com.clarify.cbo.*;
                      ...
                      Generic BoUser, BoQueue = null;
                      String strObjid = "268435464";
                      ...
                      // Set BoUser as the parent of BoQueue. The parent-child relationship
                      // represents the user_assigned2queue relation.
                      BoQueue.setParentRelation("user_assigned2queue");
                      BoQueue.setParentBO(BoUser);
                      BoUser.query();
                      ...
                      // After selecting the row for bjensen, add the queue to BoQueue.
                      BoQueue.addById(strObjid);
                      // Commit the change to the database by calling the BO.Update method
                      // on either the parent or child business object.
                      BoUser.update();




202
                                                                                       CHAPTER 4   Working with Database Relations




                     In JavaScript:
                     var strObjid = "268435464";
                     // Set BoUser as the parent of BoQueue. The parent-child relationship
                     // represents the user_assigned2queue relation.
                     BoQueue.ParentBO = BoUser;
                     BoQueue.ParentRelation = "user_assigned2queue";
                     // Query for users and queues.
                     BoUser.Query();
                     ...
                     // After selecting the row for bjensen, add the queue to BoQueue.
                     BoQueue.AddById(strObjid);
                     // Commit the change to the database by calling the BO.Update method
                     // on either the parent or child business object.
                     BoUser.Update();

                  Figure 30 illustrates the effect of executing these statements.

Figure 30     Using the BO.AddById method to establish a relation


    login_name               Parent-child       objid        title               description
                             relationship
    bdoe                                        268435457    Admin Subcases Queue for administrative subcases
    bjensen                                     268435458    Field Dispatches Queue for cases with field eng. dispatches
    bstar                                       268435459    High Priority       Queue for high priority cases
        Rowset               New relation       268435460    Prod Lit Reqs       Queue for product literature requests

BoUser (Generic business                        268435461    Questions           Queue for cases that are questions
object for the user table)                      268435462    Tier 2 Cases        Queue for tier 2 cases
                                                268435463    Urgent Cases        Queue for urgent priority cases
                                                268435464
               Newly added row                                                   Rowset
                                                                    ParentRelation: user_assigned2queue
                                                                             ParentBO: BoUser

                                                                                 Properties

 To add the row with the object ID 268435464 to the child           BoQueue (Generic business object for
 business object, call the following method:                                 the queue table)

 BoQueue.AddById("268435464");                                                         All fields in the newly copied row are
                                                                                       empty except for the object ID field.

                  If you want to replace a related row with a different row, select the row in the child business object
                  that you want to replace, and use the BO.ReplaceById method of the child business object.

                  To commit changes made by the BO.ReplaceById method, use the child business object’s
                  BO.UpdateAll method or the parent business object’s BO.Update method (or the
                  BoContext.updateAll method, if you want to commit the changes in all business objects
                  created from the BoContext object). If you just call the child business object’s BO.Update
                  method, the replaced row is not removed from the rowset, and the relation to the replaced row still
                  exists in the database.




                                                                                                                                203
Establishing Database Relations




                  For example, suppose that you want to change the queue membership for the bjensen user. You
                  want to remove bjensen from the Urgent Cases queue and add the user to the New Cases queue.
                  You set up a Generic business object for the user table (BoUser) as the parent of a Generic
                  business object for the queue table (BoQueue). If you know the object ID of the New Cases queue,
                  you can use the following statements to replace the relation to the Urgent Cases queue with a
                  relation to the New Cases queue:
                      In Java:
                      import com.clarify.cbo.*;
                      ...
                      Generic BoUser, BoQueue = null;
                      String strObjid = "268435464";
                      ...
                      // Set BoUser as the parent of BoQueue. The parent-child relationship
                      // represents the user_assigned2queue relation.
                      BoQueue.setParentRelation("user_assigned2queue");
                      BoQueue.setParentBO(BoUser);
                      BoUser.query();
                      ...
                      // After selecting the row for the bjensen user, find the row in
                      // BoQueue that you want to replace.
                      boolean bFoundMatch = BoQueue.find("title", "Urgent Cases",
                         CboConstants.cboFindFirst);
                      // Replace the selected row in BoQueue.
                      if (bFoundMatch) {
                         BoQueue.replaceById(strObjid);
                         // Commit change to the database by calling the BO.Update method
                         // on either the parent or child business object.
                         BoUser.update();
                      }
                      In JavaScript:
                      var strObjid = "268435464";
                      ...
                      // Set BoUser as the parent of BoQueue. The parent-child relationship
                      // represents the user_assigned2queue relation.
                      BoQueue.ParentBO = BoUser;
                      BoQueue.ParentRelation = "user_assigned2queue";
                      // Query for users and queues.
                      BoUser.Query();
                      ...
                      // After selecting the row for the bjensen user, find the row in
                      // BoQueue that you want to replace.
                      bFoundMatch = BoQueue.Find("title", "Urgent Cases", cboFindFirst);
                      // Replace the selected row in BoQueue.
                      if (bFoundMatch) {
                         BoQueue.ReplaceById(strObjid);
                         // Commit change to the database by calling the BO.Update method
                         // on either the parent or child business object.
                         BoUser.Update();
                      }




204
                                                                      CHAPTER 4   Working with Database Relations




When you call the BO.AddById or BO.ReplaceById methods, the row that you want to add
might be a hidden row in the rowset of the child business object. For example, in Figure 30, you
might want to associate the Admin Subcases queue with the bjensen user. The row for the Admin
Subcases queue is already in the rowset for BoQueue. The row is not exposed, since that queue is
not related to the bjensen user.

Depending on whether the row already exists in the rowset, calling the BO.AddById or
BO.ReplaceById methods has one of the following effects:
   If the row is already in the rowset but is hidden, these methods expose the existing row.
   If the row is not already in the rowset, these methods add a new row to the rowset. All fields in
   the new row are empty, except the objid field (as shown in Figure 30).

You do not need to fill in any of the empty fields before committing changes to the database. Empty
fields are ignored. If you assign new values to any of the empty fields, the new values are saved
when you commit your changes to the database.

Note: Some types of business objects require that the fields and contained business objects be filled with data.
For example, the Case business object requires a related condition before you commit any changes to the
database. If you want to modify these types of business objects, you should use the BO.Add or BO.Replace
methods instead of the BO.AddById and BO.ReplaceById methods. See Adding or Replacing a Child
by Copying on page 205 for more information on using these methods.


If you want to retrieve the values of any fields that are currently empty, you need to commit your
changes and query the database again.

Adding or Replacing a Child by Copying
You can use a parent-child relationship to establish a relation by selecting a row in the parent
business object and adding a row to the child business object. This sets up a relation between the
selected row in the parent business object and the new row in the child business object.

You can use a separate business object to find the row to add to the child business object, and then
you can use the BO.Add method of the child business object to copy the row to the child.

Note: You should use BO.Replace instead of BO.Add if the parent business object’s relation to the current
business object is an MTO relation, an OTO relation, or an exclusive relation. In this type of situation, the
child business object must have at most one exposed row. If you call BO.Add and the rowset already contains
a row, an error is thrown. If you call BO.Replace instead, any existing row is replaced. If the rowset
contains no rows, a new row is added.




                                                                                                             205
Establishing Database Relations




                  After adding the row, you need to call one of the update methods (see Committing Changes to the
                  Database on page 83) to commit the relation to the database:
                      If you just want to commit changes in the child business object, use one of the update methods
                      for that child business object (for example, ChildBO.Update).
                      If you want to commit changes in other child business objects, use one of the update methods
                      for the parent business object (for example, ParentBO.Update). This commits all changes in a
                      single transaction.

                  Note: The BO.Add method does not insert a new row in to the database table. This method just copies an
                  existing row from one business object to another in memory.


                  For example, suppose you want to associate the High Severity queue with the user bjensen. You
                  set up a Generic business object for the user table (BoUser) as the parent of a Generic business
                  object for the queue table (BoQueue). To find the row for the High Severity queue, you set up a
                  separate Generic business object for the queue table (BoQueue2). You can use the following
                  statements to relate the queue to the user:
                      In Java:
                      import com.clarify.cbo.*;
                      ...
                      Generic BoUser, BoQueue = null;
                      ...
                      // Set BoUser as the parent of BoQueue.
                      BoQueue.setParentRelation("user_assigned2queue");
                      BoQueue.setParentBO(BoUser);
                      BoUser.query();
                      ...
                      // BoQueue2 is a separate business object that you use to find
                      // the row for the High Severity queue.
                      BoQueue2.setFilter("title = 'High Severity'");
                      BoQueue2.query();
                      // After selecting the row for the bjensen user in BoUser and the row
                      // for the High Severity queue in BoQueue2, add the queue to BoQueue.
                      if (BoQueue2.getCount() > 0) {
                         BoQueue.add(BoQueue2);
                         // Commit change to the database by calling the BO.update method
                         // on either the parent or child business object.
                         BoUser.update();
                      }
                      In JavaScript:
                      // Set BoUser as the parent of BoQueue. The parent-child relationship
                      // represents the user_assigned2queue relation.
                      BoQueue.ParentBO = BoUser;
                      BoQueue.ParentRelation = "user_assigned2queue";
                      BoUser.Query();
                      ...
                      // BoQueue2 is a separate business object that you use to find
                      // the row for the High Severity queue.
                      BoQueue2.Filter = "title = 'High Severity'
                      BoQueue2.Query();
                      // After selecting the row for the bjensen user in BoUser and the row
                      // for the High Severity queue in BoQueue2, add the queue to BoQueue.



206
                                                                                      CHAPTER 4   Working with Database Relations




                     if (BoQueue2.Count > 0) {
                        BoQueue.Add(BoQueue2);
                        // Commit change to the database by calling the BO.Update method
                        // on either the parent or child business object.
                        BoUser.Update();
                     }

                  Figure 31 illustrates the effect of executing these statements.

Figure 31   Using the BO.Add method to establish a relation




    login_name                Parent-child      objid         title              description
                              relationship
    bdoe                                        268435457     Admin Subcases Queue for administrative subcases
    bjensen                                     268435458     Field Dispatches Queue for cases with field eng. dispatches
    bstar                                       268435459     High Priority      Queue for high priority cases
         Rowset                                 268435460     Prod Lit Reqs      Queue for product literature requests
                               New relation
 BoUser (Generic business                       268435461     Questions          Queue for cases that are questions
 object for the user table)                     268435462     Tier 2 Cases       Queue for tier 2 cases
                                                268435463     Urgent Cases       Queue for urgent priority cases
                                                268435464     High Severity      Queue for high severity cases
                                                                                 Rowset

                                                                  ParentRelation: user_assigned2queue
                                                                           ParentBO: BoUser

 To copy the currently selected row in                                          Properties
 BoQueue2 to the rowset in BoQueue,
 call the following method:                                      BoQueue (Generic business object for
                                                                          the queue table)
 BoQueue.Add(BoQueue2);




                                                objid         title              description
                                                268435464     High Severity      Queue for high severity cases

                                                                                  Rowset
                                                                          BoQueue2 (Generic business
                                                                           object for the queue table)

                  If you want to replace a related row with a different row, select the row that you want to replace in
                  the child business object, select the row that you want to replace it with in the separate business
                  object, and use the BO.Replace method of the child business object.

                  Note: The BO.Replace method does not delete the row from the database table. This method just copies an
                  existing row from one business object to another in memory.




                                                                                                                             207
Establishing Database Relations




                  To commit changes made by the BO.Replace method, use the child business object’s
                  BO.UpdateAll method or the parent business object’s BO.Update method (or the
                  BoContext.updateAll method, if you want to commit the changes in all business objects
                  created from the BoContext object). If you just call the child business object’s BO.Update
                  method, the replaced row is not removed from the rowset, and the relation to the replaced row still
                  exists in the database.

                  For example, suppose that you want to change the queue membership for the bjensen user. You
                  want to remove bjensen from the Urgent Cases queue and add the user to the New Cases queue.
                  You set up a Generic business object for the user table (BoUser) as the parent of a Generic
                  business object for the queue table (BoQueue). In order to find the row for the New Cases queue,
                  you set up a separate Generic business object for the queue table (BoQueue2). You can use the
                  following statements to replace the relation to the Urgent Cases queue with a relation to the New
                  Cases queue.
                      In Java:
                      import com.clarify.cbo.*;
                      ...
                      Generic BoUser, BoQueue = null;
                      ...
                      // Set BoUser as the parent of BoQueue.
                      BoQueue.setParentRelation("user_assigned2queue");
                      BoQueue.setParentBO(BoUser);
                      // Query for users and queues.
                      BoUser.query();
                      ...
                      // BoQueue2 is a separate business object that you use to find
                      // the row for the New Cases queue.
                      BoQueue2.setFilter("title = 'New Cases'");
                      BoQueue2.query();
                      // After selecting the row for the bjensen user in BoUser and the row
                      // for the New Cases queue in BoQueue2, find the row in
                      // BoQueue that you want to replace.
                      boolean bFoundMatch = BoQueue.find("title", "Urgent Cases",
                         CboConstants.cboFindFirst);
                      // Replace the relation to the Urgent Cases queue with a new relation
                      // to the New Cases queue.
                      if ((BoQueue2.getCount() > 0) && (bFoundMatch)) {
                         BoQueue.replace(BoQueue2);
                         // Commit change to the database by calling the BO.Update method
                         // on either the parent or child business object.
                         BoUser.update();
                      }
                      In JavaScript:
                      // Set BoUser as the parent of BoQueue. The parent-child relationship
                      // represents the user_assigned2queue relation.
                      BoQueue.ParentBO = BoUser;
                      BoQueue.ParentRelation = "user_assigned2queue";
                      // Query for users and queues.
                      BoUser.Query();
                      ...
                      // BoQueue2 is a separate business object that you use to find
                      // the row for the New Cases queue.
                      BoQueue2.Filter = "title = 'New Cases'
                      BoQueue2.Query();


208
                                                              CHAPTER 4   Working with Database Relations




   // After selecting the row for the bjensen user in BoUser and the row
   // for the New Cases queue in BoQueue2, find the row in
   // BoQueue that you want to replace.
   bFoundMatch = BoQueue.Find("title", "Urgent Cases", cboFindFirst);
   // Replace the relation to the Urgent Cases queue with a new relation
   // to the New Cases queue.
   if ((BoQueue2.Count > 0) && (bFoundMatch)) {
      BoQueue.Replace(BoQueue2);
      // Commit change to the database by calling the BO.Update method
      // on either the parent or child business object.
      BoUser.Update();
   }

Adding a New Empty Row
If you want to add a new row to the database and relate that row to another row, you can use a
parent-child relationship between two business objects. Select a row in the parent business object
and use the BO.AddNew method of the child business object to add a new, empty row to the child
business object. This sets up a relation between the selected row in the parent business object and
the new, empty row in the child business object.

For example, suppose that you want to create a new attachment and associate the attachment to a
case. You set up a Case business object (BoCase) as the parent of a DocInst business object
(BoDocInst). (The DocInst business object represents attachments.) You can use the following
statements to add a new attachment to the case:
   In Java:
   import com.clarify.cbo.*;
   ...
   Case BoCase = null;
   DocInst BoDocInst = null;
   ...
   // Set BoCase as the parent of BoDocInst. The parent-child
   // relationship represents the case_attch2doc_inst relation.
   BoDocInst.setParentRelation("case_attch2doc_inst");
   BoDocInst.setParentBO(BoCase);
   // Since this example just adds attachments, BoDocInst does not need
   // to query for existing attachments to the case. Prevent BoDocInst
   // from querying the database when BoCase is queried.
   BoDocInst.setQueryMode(CboConstants.cboSubmitDisabled);
   // Query for cases.
   BoCase.query();
   ...
   // After selecting the row for the case in BoCase, add a new row
   // to BoDocInst to represent the new attachment.
   BoDocInst.addNew();
   // Specify values for the required fields in the new row.
   boDocInst.getFields.getItem("title").setValue(strAttachmentName);
   boDocInst.getDocPath().addNew();
   boDocInst.getDocPath().setValue("path", strAttachmentFilePath);
   // Commit the change to the database by calling the BO.Update method
   // on either the parent or child business object.
   BoCase.update();




                                                                                                     209
Removing Database Relations




                    In JavaScript:
                    // Set BoCase as the parent of BoDocInst. The parent-child
                    // relationship represents the case_attch2doc_inst relation.
                    BoDocInst.ParentBO = BoCase;
                    BoDocInst.ParentRelation = "case_attch2doc_inst";
                    // Since this example just adds attachments, BoDocInst does not need
                    // to query for existing attachments to the case. Prevent BoDocInst
                    // from querying the database when BoCase is queried.
                    BoDocInst.QueryMode = cboSubmitDisabled;
                    // Query for cases.
                    BoCase.Query();
                    ...
                    // After selecting the row for the case in BoCase, add a new row
                    // to BoDocInst to represent the new attachment.
                    BoDocInst.AddNew();
                    // Specify values for the required fields in the new row.
                    boDocInst.Fields("title").Value = strAttachmentName;
                    boDocInst.DocPath.AddNew();
                    boDocInst.DocPath.Fields("path").Value = strAttachmentFilePath;
                    // Commit the change to the database by calling the BO.Update method
                    // on either the parent or child business object.
                    BoCase.Update();




Removing Database Relations
                 Suppose you want to remove a database relation between row X1 in the database table X and row
                 Y1 in the database table Y. If you have a business object for table X in which row X1 is the currently
                 selected row, you can do one of the following:
                    You can call the BO.UnrelateById method, specifying the database relation name and the
                    object ID of row Y1. If you use this method, you do not need to set up any parent-child
                    relationships.
                    For details, see Removing a Relation by Object ID and Name on page 211.
                    You can set up a child business object for table Y and remove row Y1 from this child business
                    object.
                    For details, see Establishing a Relation Through Parent-Child Objects on page 201.
                    Note that some business objects already have child business objects that are exposed as
                    properties. If you are working with these types of business objects, you can use these properties
                    to access the child business objects. See Using Contained Business Objects on page 216 for details.

                 If you use the business objects to remove an exclusive relation (see the Data Dictionary Guide for
                 information on exclusive relations), the focus_lowid field of the relation is set to null, and the
                 focus_type field is left unchanged. Make sure to replace the relation with another relation in the
                 exclusive set.




210
                                                                CHAPTER 4   Working with Database Relations




Removing a Relation by Object ID and Name
To remove a relation between two rows, use the BO.UnrelateById method. You need to pass in
the object ID of the related row and the database relation name as arguments to this method:
   BO.UnrelateById(objid, relation_name)

After you call this method, call the BO.Update method to commit your changes to the database.

The following example removes the user_assigned2queue relation between the current row in
a business object for the user table and the queue table row with the object ID 268435457.
   In Java:
   import com.clarify.cbo.*;
   ...
   Generic BoUser = null;
   String strObjid = "268435457";
   ...
   // Remove the relation between the user and the queue.
   BoUser.unrelateById(strObjid, "user_assigned2queue");
   // Commit the change to the database.
   BoUser.update();
   In JavaScript:
   var strObjid = "268435457";
   // Remove the relation between the user and the queue.
   BoUser.UnrelateById(strObjid, "user_assigned2queue");
   // Commit the change to the database.
   BoUser.Update();

Note that the row in the queue table does not need to be in any business object in memory.

If you want to remove that relation to all rows (not just the row specified by object ID), call the
BO.UnrelateAll method. Pass in the database relation name as an argument to this method:
   BO.UnrelateAll(relation_name)

You can also use this method to remove an MTO or an OTO relation if you don’t know the object ID
of the related row. (Calling the BO.UnrelateAll method removes the relation to that one row.)

The following example removes the supp_person2site relation between the currently selected
row in the business object BoEmployee and all related site table rows.
   In Java:
   import com.clarify.cbo.*;
   ...
   Generic BoEmployee = null;
   ...
   // Remove the relations to all rows through supp_person2site.
   BoEmployee.unrelateAll("supp_person2site");
   // Commit the change to the database.
   BoEmployee.update();
   In JavaScript:
   // Remove the relations to all rows through supp_person2site.
   BoEmployee.UnrelateAll("supp_person2site");
   // Commit the change to the database.
   BoEmployee.Update();



                                                                                                       211
Removing Database Relations




                 Removing Relations Through Parent-Child Objects
                 If two business objects are set up in a parent-child relationship, you can remove a relation between
                 the currently selected row in the parent business object and the currently selected row in the child
                 business object by removing the row in the child business object. To remove the row from the child
                 business object, you can call one of the following methods:
                    Use BO.Remove to remove the currently selected row in the child business object (see Removing
                    a Single Row on page 212).
                    Use BO.RemoveAll to remove all exposed rows in the child business object (see Removing
                    Multiple Rows on page 214).

                 Removing a Single Row
                 To remove a row, use the BO.Remove method of the child business object. This method removes
                 the currently selected row from the in-memory rowset of the child business object and removes the
                 relation to the current row in the parent business object.

                 Note: The method does not actually delete the row from the database table.


                 In order to commit the removal of the relation, you need to call the BO.Update method on the
                 parent business object or the BO.UpdateAll method on the child business object (or the
                 BoContext.updateAll method, if you want to commit the changes in all business objects
                 created from the BoContext object). Calling the BO.Update method on the child business object
                 does not commit the removal of the relation to the database (see Committing Changes to the Database
                 on page 83 for an explanation).

                 For example, suppose you set up a Generic business object for the user table (BoUser) and a
                 Generic business object for the queue table (BoQueue). You set up BoUser as the parent of
                 BoQueue, using the user_assigned2queue relation.

                 If you want to remove the relation between the queue named New Cases and the user bjensen,
                 you can select bjensen in BoUser (which exposes the queues related to that user in BoQueue)
                 and remove the New Cases queue from the rowset in BoQueue (see Figure 32).




212
                                                                                        CHAPTER 4   Working with Database Relations




Figure 32     Using the parent-child relationships to remove a database relation

                                 Parent-child
    login_name                   relationship    objid         title               description
    bdoe                                         268435457     Admin Subcases Queue for administrative subcases
    bjensen                                      268435458     Field Dispatches Queue for cases with field eng. dispatches
    bstar                        Existing        268435459     High Priority       Queue for high priority cases
                                 relations
        Rowset                                   268435460     Prod Lit Reqs       Queue for product literature requests

 BoUser (Generic business                        268435461     Questions           Queue for cases that are questions
 object for the user table)                      268435462     Tier 2 Cases        Queue for tier 2 cases
                                                 268435463     Urgent Cases        Queue for urgent priority cases
 Remove this row from the rowset to
 remove the relation to bjensen                  268435464     New Cases           Queue for new cases
                                                                                   Rowset
 To remove the relation between the
 currently selected queue and the user                             ParentRelation: user_assigned2queue
 bjensen, call the following method:                                        ParentBO: BoUser
 BoQueue.Remove();                                                             Properties

                                                             BoQueue (Generic business object for the queue table)

                  The following code removes the relation between the "New Cases" queue and the bjensen user:
                      In Java:
                      import com.clarify.cbo.*;
                      ...
                      Generic BoUser, BoQueue = null;
                      ...
                      // Set BoUser as the parent of BoQueue. The parent-child relationship
                      // represents the user_assigned2queue relation.
                      BoQueue.setParentRelation("user_assigned2queue");
                      BoQueue.setParentBO(BoUser);
                      // Query for users and queues.
                      BoUser.query();
                      ...
                      // After selecting the row for the bjensen user, find the row in
                      // BoQueue that you want to remove.
                      boolean bFoundMatch = BoQueue.find("title", "New Cases",
                         CboConstants.cboFindFirst);
                      // Remove the currently selected row in BoQueue.
                      if (bFoundMatch) {
                         BoQueue.remove();
                         // Commit change to the database by calling the BO.Update method
                         // on either the parent or child business object.
                         BoUser.update();
                      }
                      In JavaScript:
                      // Set BoUser as the parent of BoQueue. The parent-child relationship
                      // represents the user_assigned2queue relation.
                      BoQueue.ParentBO = BoUser;
                      BoQueue.ParentRelation = "user_assigned2queue";
                      // Query for users and queues.
                      BoUser.Query();
                      ...


                                                                                                                               213
Removing Database Relations




                       // After selecting the row for the bjensen user, find the row in
                       // BoQueue that you want to remove.
                       bFoundMatch = BoQueue.Find("title", "New Cases", cboFindFirst);
                       // Remove the currently selected row in BoQueue.
                       if (bFoundMatch) {
                          BoQueue.Remove();
                          // Commit change to the database by calling the BO.Update method
                          // on either the parent or child business object.
                          BoUser.Update();
                       }

                    Note that if the currently selected row in the child business object is also related to an unselected
                    row in the parent business object, the BO.Remove method just hides the currently selected row.
                    The row is not removed from the rowset. See Figure 33.

Figure 33       How BO.Remove hides rows related to other rows in parent business object
                               Parent-child
                               relationship
      login_name                                   objid        title             description
      bdoe                                         268435457    Admin Subcases Queue for administrative subcases
      bjensen                                      268435458    Field Dispatches Queue for cases with field eng. dispatches
      bstar                                        268435459    High Priority     Queue for high priority cases
          Rowset                                   268435460    Prod Lit Reqs     Queue for product literature requests

 BoUser (Generic business                          268435461    Questions         Queue for cases that are questions
 object for the user table)                        268435462    Tier 2 Cases      Queue for tier 2 cases
                                                   268435463    Urgent Cases      Queue for urgent priority cases
                                                   268435464    New Cases         Queue for new cases
                                                                                   Rowset
 Since the New Cases queue is
 also related to bstar and bdoe                                     ParentRelation: user_assigned2queue
 (other rows in the parent business                                          ParentBO: BoUser
 object), calling BoQueue.
 Remove hides the row for the                                                    Properties
 New Cases queue (rather than
 removes it).                                                             BoQueue (Generic business
                                                                           object for the queue table)


                    Removing Multiple Rows
                    If you want to remove that relation to multiple rows (not just the relation to the currently selected
                    row in the child business object), you can call the BO.RemoveAll method. This method removes
                    all currently exposed rows from the child business object, which removes the relations between
                    these rows and the currently selected row in the parent business object.

                    As is the case with the BO.Remove method, this method removes the rows from the in-memory
                    rowset of the child business object and removes the relations to the current row in the parent
                    business object.

                    Note: The method does not actually delete the rows from the database table.




214
                                                              CHAPTER 4   Working with Database Relations




In order to commit the removal of the relations, you need to call the BO.Update method on the
parent business object or the BO.UpdateAll method on the child business object (or the
BoContext.updateAll method, if you want to commit the changes in all business objects
created from the BoContext object). Calling the BO.Update method on the child business object
does not commit the removal of the relations to the database (see Committing Changes to the Database
on page 83 for an explanation).

For example, suppose that you want remove the bjensen user from any queue with the word
"case" in the title. You set up a Generic business object for the user table (BoUser) as the parent
of a Generic business object for the queue table (BoQueue). Then, you can use the following
statements to remove the relations between these queues and the bjensen user:
   In Java:
   import com.clarify.cbo.*;
   ...
   Generic BoUser, BoQueue = null;
   ...
   // Set BoUser as the parent of BoQueue.
   BoQueue.setParentRelation("user_assigned2queue");
   BoQueue.setParentBO(BoUser);
   // Set a filter on BoQueue to retrieve only those queues that contain
   // the word "case" in the title.
   BoQueue.setFilter("title like '%case%'");
   BoUser.query();
   ...
   // After selecting the bjensen user, remove all exposed rows in
   // BoQueue, which removes their relations to the bjensen user.
   BoQueue.removeAll();
   // Commit change to the database by calling the BO.update method
   // on either the parent or child business object.
   BoUser.update();
   In JavaScript:
   // Set BoUser as the parent of BoQueue. The parent-child relationship
   // represents the user_assigned2queue relation.
   BoQueue.ParentBO = BoUser;
   BoQueue.ParentRelation = "user_assigned2queue";
   // Set a filter on BoQueue to retrieve only those queues that contain
   // the word "case" in the title.
   BoQueue.Filter = "title like '%case%';
   BoUser.Query();
   ...
   // After selecting the bjensen user, remove all exposed rows in
   // BoQueue, which removes their relations to the bjensen user.
   BoQueue.RemoveAll();
   // Commit the change to the database by calling the BO.Update method
   // on either the parent or child business object.
   BoUser.Update();

If you also want remove the relations between the currently selected row and rows not present in
the rowset, pass the cboAllRelated flag (in Java, use CboConstants.cboAllRelated) as an
argument to BO.RemoveAll.




                                                                                                     215
Using Contained Business Objects




                 For example, in the previous section of code, the rowset in the child business object BoQueue
                 contains only a subset of the queues related to the bjensen user. If you want to remove the
                 relations between the bjensen user and all queues in the database, use the following statements:
                     In Java:
                     import com.clarify.cbo.*;
                     ...
                     Generic BoUser, BoQueue = null;
                     ...
                     // After selecting the bjensen user, remove relations to any queue
                     // in the database.
                     BoQueue.removeAll(CboConstants.cboAllRelated);
                     // Commit the change to the database by calling the BO.update method
                     // on either the parent or child business object.
                     BoUser.update();
                     In JavaScript:
                     // After selecting the bjensen user, remove relations to any queue
                     // in the database.
                     BoQueue.RemoveAll(cboAllRelated);
                     // Commit the change to the database by calling the BO.Update method
                     // on either the parent or child business object.
                     BoUser.Update();

                 Note that these methods also apply to business objects that contain other business objects in
                 parent-child relationships. For details, see Using Contained Business Objects on page 216.

                 The BO.Delete and BO.DeleteAll methods can also remove the existing relations between the
                 deleted rows and the currently selected row in the parent business object. However, you should be
                 careful when deleting database rows. See the section Deleting Rows from the Database on page 79 for
                 caveats on deleting database rows.




Using Contained Business Objects
                 Most types of business objects (except the Generic business object) contain child business objects.
                 These contained business objects are exposed as properties in the main business object. (Some
                 contained business objects are not child business objects. See the Core Business Objects Reference
                 Guide for more information on containment in specific types of business objects.)

                 For example, when you create a Case business object, the Case business object also automatically
                 creates child business objects for the logs related to the cases, the sites and contacts that reported
                 the cases, and the installed parts identified in the cases.

                 From the Case business object, you can access these contained business objects through the Case
                 properties. You can use the CaseBO.Contact property to access the business object for the contact
                 who reported the case. You can use the CaseBO.NotesLog property to access the business object
                 for logging notes against the case.

                 Note: Do not assume the type of the business object from the property name. For example, do not assume that
                 the CaseBO.Contact and CaseBO.Site properties refer to Contact and Site business objects.
                 These properties actually refer to Generic business objects for the contact and site tables. See the Core
                 Business Objects Reference Guide for details on the types of contained business objects.



216
                                                                                          CHAPTER 4   Working with Database Relations




                Figure 34 illustrates some of the business objects contained in the Case business object.

Figure 34   Business objects contained in the Case business object




                                                   id_number            title
                                                   56                   Need patch rel.
                                                   92                   Upgrade info
                                                   115                  Need upgrade
                                                   116                  A new case

                                                             Rowset for case table



                            Parent-child
                             relationship          site_id              name
                            representing           1                    PicoWare Inc.
                         case_reporter2site
                                                   4                    eCustomWare                             BoCase.Site
                                                                                                                 property
                                                   9                    SoftIdeas Corp.
                                                   20                   ScriptWorkings

                                                             Rowset for site table

                                               (Generic business object for the site table)



              Parent-child relationship
                   representing
               case_reporter2contact               first_name           last_name
                                                   Barbara              Jensen
                                                   Jane                 Doe                                     BoCase.Contact
                                                                                                                   property
                                                   Justin               Tylor
                                                   Robert               Jay

                                                          Rowset for contact table

                                           (Generic business object for the contact table)




                                                   description          ...
                                                                                                                BoCase.NotesLog
                                                                                                                    property
      Parent-child relationship representing
             case_notes2notes_log                        Rowset for notes_log table

                                          (Generic business object for the notes_log table)




                                  BoCase (Case business object)



                                                                                                                                 217
Using Contained Business Objects




                 These contained business objects work in the same way that child business objects work. When you
                 select a case in the rowset of the Case business object, the site and contact for that case are selected.
                 To get data on that site or contact, you access the currently selected rows in the business objects
                 returned by the CaseBO.Contact and CaseBO.Site properties.

                 Similarly, if you want to add a note and relate the note to a case, you select the case in the rowset of
                 the Case business object, and you add a new row to the business object returned by the
                 CaseBO.NotesLog property. When you commit your changes to the case or notes, the notes are
                 automatically related to the case.

                 For information on using contained business objects, see the following sections:
                     Getting Data from Contained Business Objects on page 218
                     Using Contained Objects to Establish Relations on page 220

                 For information on each type of business object and its containment of other business objects, see
                 the Core Business Objects Reference Guide.


                 Getting Data from Contained Business Objects
                 You can use the properties of the main business object to access data from the contained objects. For
                 example, the Case business object has a Contact property that is a reference to the contained
                 Generic business object for the contact table. The following section of code gets the first and
                 last name of the contact who logged a case.
                     In Java:
                     import com.clarify.cbo.*;
                     ...
                     Case BoCase = null;
                     String strFirstName, strLastName;
                     ...
                     strFirstName =
                        BoCase.getContact().getValue("first_name").toString();
                     strLastName =
                        BoCase.getContact().getValue("last_name").toString();
                     In JavaScript:
                     var strFirstName, strLastName;
                     ...
                     strFirstName = BoCase.Contact("first_name").Value;
                     strLastName = BoCase.Contact("last_name").Value;

                 Because some of the contained business objects can retrieve large amounts of data when the main
                 business object is queried, many contained business objects have the BO.DataFields and
                 BO.QueryMode properties set to minimize the amount of data returned on a query of the main
                 business object.

                 For example, when creating the Generic business object accessible through the
                 CaseBO.Contact property, the Case business object sets the DataFields property of the
                 contained business object to "first_name,last_name". When you query the Case business
                 object, the contained business object for the contact table returns data only for these two fields.

                 If you want to get the value of any other field, you need to change the value of the
                 CaseBO.Contact.DataFields property and query the database again.



218
                                                            CHAPTER 4   Working with Database Relations




The following example gets the email address of the contact who reported a case.
   In Java:
   import com.clarify.cbo.*;
   ...
   Case BoCase = null;
   String strEmail;
   ...
   // Change the DataFields property to include the e_mail field.
   if (BoCase.getContact().getDataFields().equals("")) {
      BoCase.getContact().setDataFields("e_mail");
   } else {
      String strDataFields = BoCase.getContact().getDataFields();
      strDataFields += ",e_mail";
      BoCase.getContact().setDataFields(strDataFields);
   }
   // Query the Case business object again.
   BoCase.query();
   strEmail = BoCase.getContact().getValue("e_mail").toString();
   In JavaScript:
   var strEmail;
   ...
   // Change the DataFields property to include the e_mail field.
   if (BoCase.Contact.DataFields = "") {
      BoCase.Contact.DataFields = "e_mail";
   } else {
      BoCase.Contact.DataFields += ",e_mail";
   }
   // Query the Case business object again.
   BoCase.Query();
   strEmail = BoCase.Contact("e_mail").Value;

As another example, when creating the NotesLog business object accessible through the
CaseBO.NotesLog property, the Case business object sets the QueryMode property of the
contained business object to cboSubmitDisabled. When you query the Case business object, the
contained NotesLog business object is not included in the query operation and does not return
any rows.

To get the existing notes logged against cases, you need to set the Case.NotesLog.QueryMode
property to cboSubmitEnabled (in Java, use CboConstants.cboSubmitEnabled) and query
the database again.




                                                                                                   219
Using Contained Business Objects




                 For example, the following section of code gets notes logged against cases.
                     In Java:
                     import com.clarify.cbo.*;
                     ...
                     Case BoCase = null;
                     ...
                     // Change the QueryMode property to CboConstants.cboSubmitEnabled.
                     BoCase.getNotesLog().setQueryMode(CboConstants.cboSubmitEnabled);
                     // Query the Case business object again.
                     BoCase.query();
                     // Now you can access existing logs through BoCase.getNotesLog().
                     ...
                     In JavaScript:
                     // Change the QueryMode property to cboSubmitEnabled.
                     BoCase.NotesLog.QueryMode = cboSubmitEnabled;
                     // Query the Case business object again.
                     BoCase.Query();
                     // Now you can access existing logs through BoCase.NotesLog.
                     ...

                 For the default settings of the DataFields and NotesLog properties for each contained business
                 object in the different types of business objects, see the Core Business Objects Reference Guide.


                 Using Contained Objects to Establish Relations
                 Because most contained business objects are child business objects of the main business object, you
                 can use the contained business objects to establish and remove relations (see Establishing a Relation
                 Through Parent-Child Objects on page 201 and Removing Relations Through Parent-Child Objects on
                 page 212).

                 For example, to add a new note to a case, you select the case in the Case business object and call
                 the CaseBO.NotesLog.AddNew method. The following statements demonstrate how to do this:
                     In Java:
                     import com.clarify.cbo.*;
                     ...
                     Case BoCase = null;
                     String strNotes = null;
                     ...
                     // Add a new row to the contained NotesLog business object.
                     BoCase.getNotesLog().addNew();
                     // Add the notes to the new row.
                     BoCase.getNotesLog().setValue("description", strNotes);
                     // Commit your changes to the database.
                     BoCase.update();
                     In JavaScript:
                     // Add a new row to the contained NotesLog business object.
                     BoCase.NotesLog.AddNew();
                     // Add the notes to the new row.
                     BoCase.NotesLog.Fields("description").Value = strNotes;
                     // Commit your changes to the database.
                     BoCase.Update();



220
                                                             CHAPTER 4   Working with Database Relations




Note that some types of business objects require you to establish relations before committing new
rows to the database. For example, before committing a new case to the database, you need to
associate the case with a contact. Before committing a phone log to the database, you need to
associate the phone log with a contact. (Otherwise, the act_entry.addnl_info field cannot
specify the contact associated with the phone log.)




                                                                                                    221
Using Contained Business Objects




222
C H A P T E R     5




Working with Database Views



CONTENTS
 Overview: Database Views and Business Objects 224

 Using a View to Perform a Query 224

 Setting Up a Business Object for a Related Table 230

 Setting Up an Updatable View 232

 Setting Up a Child Business Object Hierarchy 237




                                                        223
Overview: Database Views and Business Objects




Overview: Database Views and Business Objects
                 You can use the Generic business object to query any ClarifyCRM database view. In addition, you
                 can also set up the business objects to update data in the tables joined by a view. Depending on
                 whether you just want to read data from the view or update the data returned by the view, you
                 need to set up the business objects in different ways:
                     To set up the business objects to query and read data from a view, you can use a Generic
                     business object.
                     For details, see the section Using a View to Perform a Query on page 224.
                     To set up the business objects to query and read data from a table related to the base table of the
                     view, you can create a child business object representing the related table.
                     For more information, see the section Setting Up a Business Object for a Related Table on page 230.
                     To set up the business objects to update fields in data returned by the view, you also need to
                     create child business objects representing the tables containing the fields that you want to
                     modify.
                     For more information, see the section Setting Up an Updatable View on page 232.
                     To set up the business objects to update relations between the tables used by the view, you need
                     to create a parent-child hierarchy of objects that define the relations that you want to update.
                     For details, see the section Setting Up a Child Business Object Hierarchy on page 237.




Using a View to Perform a Query
                 To query a ClarifyCRM database view, use the Generic business object. Set the
                 BO.DBObjectName property to the name of the database view, and set the other properties (for
                 example, BO.DataFields, BO.Filter, and BO.SortOrder) to the appropriate criteria.

                 For example, the following section of code queries the rol_contct database view for rows. The
                 query finds sites with names that start with ClarifyCRM. The query sorts the results first by site
                 name, then by first and last name of the contact.
                     In Java:
                     import com.clarify.cbo.*;
                     ...
                     Generic boView = null;
                     System.out.println("Getting a business object...");
                     boView = clfyBoContext.createGenericBO("rol_contct");
                     boView.setDataFields("*");
                     boView.setFilter("site like 'Clarify*'");s
                     boView.setSortOrder("site, first_name, last_name");
                     boView.query();
                     In JavaScript:
                     var BoView;
                     BoView = ClfyForm.CreateGenericBO("rol_contct");
                     BoView.DataFields = "*";
                     BoView.Filter = "site like 'Clarify%'"
                     BoView.SortOrder = "site, first_name, last_name"
                     BoView.Query();


224
                                                                   CHAPTER 5   Working with Database Views




You can also set the BO.FilterRelated and BO.SortOrderRelated properties. When you set
these for a business object for a view, the path specified by these properties starts at the base table
for the view. (For more information on the syntax for the BO.FilterRelated and
BO.SortOrderRelated properties, see Specifying a Search Filter for Related Tables on page 47 and
Specifying Sort Order on page 49.)

To determine the base table for a view, check the Data Dictionary for Views for the specification for
the view, and find the field marked as the unique field in the view. The table containing this unique
field is the base table for the view.

For example, in the rol_contct view, the contact_role table is the base table. (The object ID of
the contact_role table is the unique field in the rol_contct view.) If you specify a path in the
BO.FilterRelated and BO.SortOrderRelated properties, the path begins from the
contact_role table.

The following example in Java demonstrates how to query the rol_contct view for contacts that
have a web user login name containing the word user. The results are sorted by the web user login
name.
   In Java:
   import com.clarify.cbo.*;
   ...
   Generic boGeneric = null;
   ...
   // Create a Generic business object for the rol_contct view.
   Generic boGeneric = clfyBoContext.createGenericBO("rol_contct");
   boGeneric.setDataFields("*");
   // Specify that the query only returns rows where the contact’s
   // web user login contains the word user.
   boGeneric.setFilterRelated(
      "contact_role2contact:contact2web_user;login_name like
      '%user%'");
   // Sort the results by the contact’s web user login name.
   boGeneric.setSortOrderRelated(
      "contact_role2contact:contact2web_user;login_name");
   boGeneric.query();
   ...
   In JavaScript:
   ...
   // Create a Generic business object for the rol_contct view.
   var boGeneric = clfyForm.CreateGenericBO("rol_contct");
   boGeneric.DataFields= "*";
   // Specify that the query only returns rows where the contact’s
   // web user login contains the word user.
   boGeneric.FilterRelated =
      "contact_role2contact:contact2web_user;login_name like
      '%user%'";
   // Sort the results by the contact’s web user login name.
   boGeneric.SortOrderRelated =
      "contact_role2contact:contact2web_user;login_name";
   boGeneric.Query();
   ...

When you retrieve the results of this query, the fields in the rowset are read-only. If you attempt to
commit any changes to the fields in a view, an error is raised.


                                                                                                      225
Using a View to Perform a Query




                   If you want to modify the data returned by a view, you need to set up a business object for that type
                   of data. You can modify data in that separate business object and commit your changes to the
                   database.

                   You can use a number of different techniques to set up separate business objects:
                       You can set up child business objects under the business object for the view. When you query
                       the view, the child business objects are filled with data from the view. You can use the child
                       business objects to update that data.
                       For details, see Setting Up an Updatable View on page 232 and Setting Up a Child Business Object
                       Hierarchy on page 237.
                       You can set up a separate business object for the data that you want to modify, and you can use
                       the view results to query the business object for the row that you want to update.
                       For details, see Using a Separate Object to Query and Update Data on page 226.


                   Using a Separate Object to Query and Update Data
                   Suppose that you query the cas_secure view and you want to modify the case information
                   returned by the view. You can set up a separate Case business object to update case information.
                   Using the case_id field of the cas_secure view in the filter for the Case business object, you
                   can query the database for the case that you want to modify. (See Figure 35.)

Figure 35    Using a separate business object to update data from a view

BoView (Generic business object for the cas_secure view)


      case_objid   case_id title                        ...
      268435457    1         Problem with upgrade ...
      268435458    2         Need latest manuals        ...
      268435459    3         Error when logging in ...
                                                                                   BoCase (Case business object)
                           Rowset
                                                                                      Filter: id_number = 3
1. Query BoView
   to get the rows                      2. Use the case ID                                 Properties
   from the                                from the view as
   database view.                          a filter in BoCase.
                                                                   objid       id_number    title                  ...
                                                                   268435459   3            Error when logging in ...
                            3. Query BoCase to get
                               the row for that case.                                       Rowset


                                                                  4. Make your changes in BoCase.



                                                          5. Commit your changes
                                                             to the database.

            cas_secure View           case Table

                        ClarifyCRM Database


226
                                                                  CHAPTER 5   Working with Database Views




The following section of code demonstrates how to use a separate Case business object to update
data retrieved from the cas_secure view.
   In Java:
   import com.clarify.cbo.*;
   ...
   Generic boView = null;
   Case boCase = null;
   ...
   boView = clfyBoContext.createGenericBO("cas_secure");
   boView.setDataFields("*");
   boView.setFilter("first_name = 'Barbara' and last_name = 'Jensen'");
   boView.setSortOrder("case_id");
   // Query the cas_secure view.
   boView.query();
   // Set up a Case business object.
   boCase = (Case) clfyBoContext.createBO("com.clarify.cbo.Case");
   boCase.setDataFields("*");
   boCase.setFilter("id_number = '" +
      boView.getFields().getItem("case_id").getValue() + "'");
   boCase.query();
   // Make your changes in boCase.
   ...
   // Commit your changes to the database.
   boCase.update();
   In JavaScript:
   ...
   var BoView, BoCase
   // Set up a Generic business object for the cas_secure view.
   BoView = ClfyForm.CreateGenericBO("cas_secure");
   BoView.DataFields = "*";
   BoView.Filter = "first_name = 'Barbara' and last_name = 'Jensen'";
   BoView.SortOrder = "case_id";
   // Query the cas_secure view.
   BoView.Query();
   // Set up a Case business object.
   BoCase = ClfyForm.CreateBO("Clarify.CBO.Case");
   BoCase.DataFields = "*";
   BoCase.Filter = "id_number = '" + BoView("case_id").Value + "'";
   BoCase.Query();
   // Make your changes in BoCase.
   ...
   // Commit your changes to the database.
   BoCase.Update();
   ...

Note that unlike the technique described in Setting Up an Updatable View on page 232 and Setting Up
a Child Business Object Hierarchy on page 237, this technique requires two roundtrips to the database
(one for the view query and one for the case query).

In certain situations, you can avoid the second query by using the BO.AddById method. See Using
AddById to Avoid an Additional Query on page 228.




                                                                                                     227
Using a View to Perform a Query




                  Using AddById to Avoid an Additional Query
                  Using a Separate Object to Query and Update Data on page 226 describes how to set up a separate
                  business object for updating data retrieved from a database view. This technique requires two
                  database queries: one query for the view, and one query for the separate business object.

                  If the view contains the object ID field of the database table that you want to update, you can avoid
                  the second database query by using the BO.AddById method. You can copy the object ID from the
                  view to the separate business object and make changes in that business object. Because the newly
                  added row contains the object ID of the row that you are modifying, you do not need to query the
                  database for that row.

                  Note: This technique does not work for some types of business objects. See the end of this section for details.


                  For example, suppose that you query the qry_owner view (which returns a join of stored queries
                  and the users who own those queries) and you want to modify the titles of the stored queries
                  returned by the view. You can set up a separate Generic business object for the query table
                  (BoQuery) to update the titles of the stored queries. Rather than perform a second roundtrip to the
                  database to get the row to modify, use BoQuery.AddById method to add that row. (See Figure 36.)

Figure 36    Using BO.AddById to avoid a database query when updating view data

                        BoView (Generic business object
                        for the qry_owner view)


                    objid         title                    ...
                    268435457     High Priority Cases      ...
                    268435458     Administrative Cases ...
                    268435459     My Latest Cases          ...

                                      Rowset

            1. Query BoView to            2. Use BoQuery.AddById to             BoQuery (Generic business object
               get the rows from             add a row for the stored           for the query table)
               the database view.            query that you want to
                                             modify.
                                                                        objid          title                  ...
                                                                        268435459

                                                                                               Rowset

                                                                        3. Make your changes in BoQuery.


                                                                        4. Commit your changes to
                                                                           the database.

                            qry_owner View            query Table

                                    ClarifyCRM database

                  Although most of the fields in the newly added row are empty, the row does include the object ID,
                  which the business object uses to find and update the corresponding row in the database.


228
                                                                CHAPTER 5   Working with Database Views




The following section of code demonstrates how to use a separate Generic business object for the
query table to update data retrieved from the qry_owner view.
   In Java:
   import com.clarify.cbo.*;
   ...
   Generic boView, boQuery = null;
   ...
   boView = clfyBoContext.createGenericBO("qry_owner");
   boView.setDataFields("*");
   boView.setFilter("owner_objid = '" + clfySession.getItem("user.id")
      + "'");
   boView.setSortOrder("title");
   boView.query();
   // Create a separate business object to modify the query table.
   System.out.println("Setting up generic object for query...");
   boQuery = clfyBoContext.createGenericBO("query");
   boQuery.setDataFields("*");
   // Copy the object ID of the currently selected view row.
   System.out.println("Copying objid of current row...");
   Integer nObjid =
      (Integer)boView.getFields().getItem("objid").getValue();
   boQuery.addById(nObjid.toString());
   // Set a new title for this row.
   boQuery.getFields().getItem("title").setValue(strNewQueryTitle);
   ...
   In JavaScript:
   ...
   var BoView, BoQuery, strNewQueryTitle;
   ...
   // Set up a Generic business object for the qry_owner view.
   BoView = ClfyForm.CreateGenericBO("qry_owner");
   BoView.DataFields = "*";
   // Only retrieve the stored queries owned by the current user.
   BoView.Filter =
      "owner_objid = '" + ClfySession.Item("user.id") + "'";
   BoView.SortOrder = "title";
   // Query the business objects.
   BoView.Query();
   // Create a separate business object to modify the query table.
   BoQuery = ClfyForm.CreateGenericBO("query");
   BoQuery.DataFields = "*";
   // Copy the object ID of the currently selected view row.
   BoQuery.AddById(BoView("objid").Value);
   // Set a new title for this row.
   BoQuery("title").Value = strNewQueryTitle;
   // Commit your changes to the database.
   BoQuery.Update();
   ...

Although this technique saves one roundtrip to the database (you do not need to query the
separate business object), this technique does not work with some types of business objects because
of the empty fields in the added row.




                                                                                                   229
Setting Up a Business Object for a Related Table




                  Some examples of these types business objects include:
                      the Case business object
                      When you call the CaseBO.Update method, the Case business object expects the contained
                      Condition business object to expose the condition related to the currently selected case. Since
                      BO.AddById does not fill in any contained business objects or fields other than the object ID, an
                      error is thrown if you attempt to update this row.
                      the Contact business object
                      When you call the ContactBO.Update method, the Contact business object expects the
                      contained Site business object to expose the site related to the currently selected contact. Since
                      BO.AddById does not fill in any contained business objects or fields other than the object ID, an
                      error is thrown if you attempt to update this row.
                      the Site business object
                      When you call the SiteBO.Update method, the Site business object expects the contained
                      Generic business object for the address table to expose the primary address related to the
                      currently selected site. Since BO.AddById does not fill in any contained business objects or
                      fields other than the object ID, an error is thrown if you attempt to update this row.

                  Other types of business objects may also require nonempty fields and contained business objects in
                  order to update a row.




Setting Up a Business Object for a Related Table
                  If you need to get and update data in a table related to the base table in a view (see Using a View to
                  Perform a Query on page 224 for a discussion of views and base tables), you can set up a
                  parent-child relationship between the business object for the view and the business object for the
                  related table. (For more information on parent-child relationships, see Understanding Parent-Child
                  Relationships on page 180.)

                  To determine the base table for a view, check the Data Dictionary for Views for the specification for
                  the view, and find the field marked as the unique field in the view. The table containing this unique
                  field is the base table for the view.

                  To set up this type of parent-child relationship, do the following:
                   1. Create a parent business object for the view.
                   2. Create a child business object for the table related to the base table in the view.
                   3. Set the ChildBO.ParentRelation to the relation between the base table and the table for
                      ChildBO. (The relation name should start from the base table.)

                  When you query the parent business object, the child business object retrieves the rows related to
                  the corresponding rows in the parent business object.

                  Note: The child business object retrieves all the fields that you specify in the BO.DataFields property,
                  regardless of whether the field is available in the view. If you set the BO.QueryUsingView property to
                  True, the child business object returns only values for the fields available in the view.




230
                                                             CHAPTER 5   Working with Database Views




The following example demonstrates how to use a parent-child relationship to query the
extactcase view (using a parent business object) and the contact table (using a child business
object). Since the base table for the extactcase view is the case table, you need to set the
ChildBO.ParentRelation property to the relation from the case table (the base table) to the
contact table (the related table).
   In Java:
   import com.clarify.cbo.*;
   ...
   Generic boParent = null;
   Generic boChild = null;
   ...
   // Set up the parent business object for the extactcase view.
   boParent = clfyBoContext.createGenericBO("extactcase");
   boParent.setDataFields("*");
   boParent.setSortOrder("id_number");
   // Set up the child business object for the contact table.
   boChild = clfyBoContext.createGenericBO("contact");
   boChild.setDataFields("*");
   boChild.setParentBO(boParent);
   // Specify the relation from the base table (case) in the view
   // to the related table (contact).
   boChild.setParentRelation("case_reporter2contact");
   // Query the view.
   boParent.query();
   ...
   In JavaScript:
   ...
   // Set up the parent business object for the extactcase view.
   var boParent = clfyForm.CreateGenericBO("extactcase");
   boParent.DataFields = "*";
   boParent.SortOrder = "id_number";
   // Set up the child business object for the contact table.
   var boChild = clfyForm.CreateGenericBO("contact");
   boChild.DataFields = "*";
   boChild.ParentBO = boParent;
   // Specify the relation from the base table (case) in the view
   // to the related table (contact).
   boChild.ParentRelation = "case_reporter2contact";
   // Query the view.
   boParent.Query();
   ...




                                                                                                231
Setting Up an Updatable View




Setting Up an Updatable View
                 One way to update the data retrieved by a view query is to set up child business objects under the
                 business object for the view.

                 For each table containing data that you want to update, set up a child business object. When you
                 query the business object for the view, the child business objects are filled with the results from the
                 view query. In addition, if those child business objects contain other business objects (see Using
                 Contained Business Objects on page 216 for more information), separate query operations are
                 performed to retrieve the data for those contained objects from the database. (Fields in the
                 contained business objects are not filled from the query results of the view.)

                 After you query the business object for the view, you can use the child business objects to modify
                 the view data and commit changes to the database.

                 If you want to use this technique, note the following guidelines and restrictions:
                     Restrictions for Child Business Objects Under Views on page 235
                     How Unique Fields Affect Child Business Objects on page 235
                     Guidelines for Child Business Objects Under Views on page 236

                 To set up child business objects under a view, follow this procedure:
                  1. Create a Generic business object for the view, and set the BO.DBObjectName property to the
                     name of a database view.
                  2. Create business objects for the tables containing the data that you want to modify.
                  3. In each business object that you created in Step 2, set the BO.ParentBO or BO.ParentName
                     property to the business object representing the view.
                     Do not set the ParentRelation property of the child business object.
                  4. In each child business object, set the BO.QueryUsingView property to True.
                  5. In each child business object, set the BO.DataFields property to the names of fields that you
                     want to retrieve.
                     Specify the field names as they appear in the database table, not as they appear in the view. (The
                     view field names can be different than their corresponding table field names.)
                  6. If a given database table has multiple instances in the view, set the BO.Alias property of the
                     child business object to identify the instance that the object represents.
                     For example, the partbom_view view (which displays the parts in a bill of materials, or BOM)
                     uses two instances of the mod_level table to represent the parent and child of a part number
                     revision. If you are setting up child business objects for the mod_level table, you need to set
                     the BO.Alias property of each business object to the corresponding alias.
                     To determine the aliases used in a view, see the Data Dictionary Guide or check the schema file
                     for the alias attribute of the <alias> tag.




232
                                                                                            CHAPTER 5   Working with Database Views




                  7. Set any other properties that you want, and call the BO.Query method of the business object
                     representing the view.
                      The BO.Filter, BO.SortOrder, BO.FilterRelated, and BO.SortOrderRelated
                      properties are ignored in the child business objects.
                  8. After getting the results of the query, use the child business objects to modify the data, and
                     commit your changes to the database.

                  For example, suppose that you use a Generic business object (BoView) to query the qry_owner
                  view (which returns a join of stored queries and the users who own those queries). If you want to
                  modify the titles of the stored queries returned by the view, you can set up a Generic business
                  object for the query table (BoQuery) as the child of BoView. Then, you can use BoQuery to
                  change the stored query titles. (See Figure 37.)

Figure 37     Example of setting up child business objects under a view

                                                      2.     Since BoQuery is a
                                                            child of BoView, the
                                                            query automatically
    objid           title                    ...            fills BoQuery with      objid          title
    268435457       High Priority Cases      ...            data from the query     268435457      High Priority Cases
                                                            table.
    268435458       Administrative Cases ...                                        268435458      Administrative Cases
    268435459       My Latest Cases          ...      Parent-child relationship     268435459      My Latest Cases

                        Rowset                                                                     Rowset
            BoView (Generic business object          3. Make your changes
                                                        in BoQuery.                          ParentBO: BoView
                for the qry_owner view)                                                     QueryUsingView: True
                                                                                              DataFields: title
                            1. Query BoView to
                               get the rows from                                                   Properties
                               the database view.
                                                                                     BoQuery (Generic business object
                                                                                           for the query table)



                                                           4. Commit your changes
                                                              to the database.


              qry_owner View           query Table

                      ClarifyCRM database




                                                                                                                               233
Setting Up an Updatable View




                 The following section of code demonstrates how to use a child business object for the query table
                 to update data retrieved from the qry_owner view.
                     In Java:
                     import com.clarify.cbo.*;
                     ...
                     Generic boView, boQuery = null;
                     ...
                     boView = clfyBoContext.createGenericBO("qry_owner");
                     boView.setDataFields("*");
                     // Retrieve only the stored queries owned by the current user.
                     boView.setFilter("owner_objid = '" +
                        clfySession.getItem("user.id") + "'");
                     boView.setSortOrder("title");
                     // Set up a Generic business object for stored queries.
                     boQuery = clfyBoContext.createGenericBO("query");
                     // Restrict fields returned to those fields available in the view.
                     boQuery.setDataFields("title");
                     // Set up the business object as a child of the business object for
                     // the view.
                     boQuery.setParentBO(boView);
                     boQuery.setQueryUsingView(true);
                     // Query the business object for the view.
                     boView.query();
                     // Set a new title for this row.
                     boQuery.getFields().getItem("title").setValue(strNewQueryTitle);
                     // Commit your changes to the database.
                     boView.update();
                     ...
                     In JavaScript:
                     ...
                     var BoView, BoQuery, strNewQueryTitle;
                     ...
                     // Set up a Generic business object for the qry_owner view.
                     BoView = ClfyForm.CreateGenericBO("qry_owner");
                     BoView.DataFields = "*";
                     // Retrieve only the stored queries owned by the current user.
                     BoView.Filter =
                        "owner_objid = '" + ClfySession.Item("user.id") + "'";
                     BoView.SortOrder = "title";
                     // Set up a Generic business object for stored queries.
                     BoQuery = ClfyForm.CreateGenericBO("query");
                     // Restrict fields returned to those fields available in the view.
                     BoQuery.DataFields = "title";
                     // Set the business object as a child of the view business object.
                     BoQuery.ParentBO = BoView;
                     BoQuery.QueryUsingView = true;
                     // Query the business object for the view.
                     BoView.Query();
                     // Set a new title for this row.
                     BoQuery("title").Value = strNewQueryTitle;
                     // Commit your changes to the database.
                     BoView.Update();
                     ...




234
                                                                    CHAPTER 5   Working with Database Views




Restrictions for Child Business Objects Under Views
Note that this technique has several restrictions:
   Because each child business object must have an object ID field, the view must contain the
   object ID field for the tables of those child business objects.
   For example, the rol_contct view does not contain the object ID field for the country table.
   As a result, you cannot create a child business object for the country table under a business
   object for the rol_contct view.
   Some types of business objects may internally use fields not specified in the view. If you set up
   one of these business objects as the child of a business object for a view, an error is raised when
   you query the view business object.
   For example, the Site business object uses the site.status database field internally, so this
   field must be available to the business object. This field is not available in the rol_contct
   view. If you set up a Site business object as the child of a business object for the rol_contct
   view, an error is raised when you attempt to query the business object for the view.
   As another example, business objects always retrieve fields that contain foreign keys, such as
   the respprty2gbst_elm field in the Case business object. (See Specifying the Fields to Retrieve
   on page 43.) The cas_secure view does not contain this field. If you set up a Case business
   object as the child of a business object for the cas_secure view, an error is raised when you
   attempt to query the business object for the view.
   Although you can set up Generic business objects instead of specific types of business objects
   (like the Case or Site business objects), note that Generic business objects don’t include the
   application logic that specific types of business objects contain. (See Using Generic Business
   Objects on page 27 for details.)


How Unique Fields Affect Child Business Objects
When you use business objects in this configuration, the business objects use the field marked as
UNIQUE (or with the generic field ID 3) to determine uniqueness. If you select a row in the
business object for the view, the child business objects expose the data related to the row identified
by that field.

For example, the cas_secure view joins the case, contact, contact_role, and site tables to
provide quick access to the cases for a given contact. In this view, the role_objid field (the
contact_role object ID) is marked as unique, although the view contains multiple rows for the
same contact role. (Each row uniquely identifies a case and its related contact, contact role, and site
information. As a result, a contact role has a row for each case associated with that contact and site.)

If you select the contact role and case object IDs from the view, you may see results like the
following:
role_objid          case_objid
-----------         -----------
268435458           268435457
268435458           268435458
268435458           268435459




                                                                                                       235
Setting Up an Updatable View




                    Suppose that you set up a Generic business object for the cas_secure view (BoCasSecure)
                    and a Case business object (BoCase) as the child of BoCasSecure. When you select a given row
                    in BoCasSecure, the rowset in BoCase exposes all the cases logged by the selected contact, not
                    just the case specified by the selected row in BoCasSecure.

                    Using the SQL results shown in the preceding example, suppose that you select the third row in
                    BoCasSecure, which represents the contact role with the object ID 26843458 and the case with the
                    object ID 26843459. BoCase exposes rows for three cases (with the object IDs 26843457, 26843458,
                    and 26843459) instead of just the row corresponding to the currently selected row in the view (the
                    case with the object ID 26843459). See Figure 38.

Figure 38     How unique fields in a view affect child business objects

                                                    Parent-child relationship
      role_objid    case_objid   ...                                                objid          title
                                                The currently selected row
      268435458     268435457    ...            identifies the case with the        268435457      High Priority Cases
                                               object ID 268435459 and the
      268435458     268435458    ...           contact role with the object ID      268435458      Administrative Cases
      268435458     268435459    ...                     268435458.                 268435459      My Latest Cases

                   Rowset                                                                          Rowset
                                                 The child business object
   BoCasSecure (Generic business               exposes all rows associated                  ParentBO: BoCasSecure
    object for the cas_secure view)            with that contact role (not just              QueryUsingView: True
                                                  the selected case in the                      DataFields: title
                                                          parent).
                                                                                                  Properties
                                                                                      BoCase (Case business object)

                    Before you set up child business objects under a view, check the view fields to make sure you
                    understand how the business objects will handle the view.


                    Guidelines for Child Business Objects Under Views
                    Note the following guidelines when working with child business objects for views:
                       As is the case with standard parent-child relationships, the BO.QueryMode and
                       BO.UpdateMode properties of a child business object determine whether that business object is
                       queried and updated automatically with the business object for the view.
                       If you do not want the child business objects queried and updated when the view object is
                       queried and updated, set the BO.QueryMode and BO.UpdateMode properties to
                       cboSubmitDisabled (in Java, use CboConstants.cboSubmitDisabled).
                       You cannot call the BO.Query method for the child business objects.
                       If you want to query the database, you must use the business object representing the view to call
                       the BO.Query method. Make sure that the BO.QueryMode properties of the child business
                       objects are set to cboSubmitEnabled (in Java, use CboConstants.cboSubmitEnabled).
                       To commit changes in the child business object, call the BO.Update and BO.UpdateAll
                       methods for the child business objects (or the BoContext.updateAll method, if you want to
                       commit the changes in all business objects created from the BoContext object).
                       You can also call these methods for the business object representing the view. If the child
                       business objects have the BO.UpdateMode property set to cboSubmitEnabled, updating the
                       business object for the view will update the child business objects.


236
                                                                                        CHAPTER 5   Working with Database Views




Setting Up a Child Business Object Hierarchy
                If you want to modify or use the relations between the child business objects in a view, you can
                organize the child business objects hierarchically. You can use the child business objects to change
                the relations between the rows or to establish new relations. For all child business objects in the
                hierarchy, set the BO.QueryUsingView property to True.

                For example, suppose that you use a Generic business object (BoView) to query the
                qry_grp_owner view (which joins groups of stored queries and the users who own those
                queries). If you want to remove the stored queries from a group, you can set up a hierarchy of child
                business objects for the query table (BoQuery) and the qry_grp table (BoQryGrp). Then, you can
                use these child business objects to remove relations between these tables (Figure 39).

Figure 39   Example of organizing the child business objects hierarchically


                                    objid         title                   grp_objid     grp_title               ...
               BoView               268435457     High Priority Cases     268435458     Cases To Check          ...
     (Generic business object
      for the qry_owner view)       268435459     My Latest Cases         268435457     Recent Cases            ...
                                    268435459     My Latest Cases         268435458     Cases To Check          ...

                                                                         Rowset


                                                                                               Parent-child relationship
                                                   objid         title
                                                   268435457     High Priority Cases
                                                   268435458     Administrative Cases
     BoQuery (Generic business object              268435459     My Latest Cases
           for the query table)
                                                                 Rowset

                                                            ParentBO: BoView
                                                           QueryUsingView: True
                                                             DataFields: title
                                                                Properties

                                                                                               Parent-child relationship



                                                   objid         title
                                                   268435457     Recent Cases
                                                   268435458     Cases To Check

     BoQryGrp (Generic business object                           Rowset
           for the qry_grp table)
                                                          ParentBO: BoQuery                     You can add or remove rows
                                                      ParentRelation: query2qry_grp             here to establish or remove
                                                         QueryUsingView: True                   relations between a query
                                                             DataFields: title                  group and the stored query
                                                                                                selected in BoQuery.
                                                                Properties




                                                                                                                           237
Setting Up a Child Business Object Hierarchy




                  The following section of code demonstrates how to use a hierarchy of child business objects for the
                  query and qry_grp tables to remove the stored query named Web Contact Cases from all query
                  groups.
                      In Java:
                      import com.clarify.cbo.*;
                      ...
                      Generic boView, boQuery, boQryGrp = null;
                      ...
                      boView = clfyBoContext.createGenericBO("qry_grp_owner");
                      boView.setDataFields("*");
                      // Retrieve only the stored queries owned by the current user.
                      boView.setFilter("owner_objid = '" + clfySession.getItem("user.id")
                         + "'");
                      boView.setSortOrder("grp_title, title");
                      // Set up a Generic business object for stored queries.
                      boQuery = clfyBoContext.createGenericBO("query");
                      // Restrict the fields returned to those
                      // fields available in the view.
                      boQuery.setDataFields("title");
                      // Set up the business object as a child of the business object
                      // for the view.
                      boQuery.setParentBO(boView);
                      boQuery.setQueryUsingView(true);
                      // Set up a Generic business object for query groups.
                      boQryGrp = clfyBoContext.createGenericBO("qry_grp");
                      // Restrict the fields returned to those
                      // fields available in the view.
                      boQryGrp.setDataFields("title");
                      // Set up the business object as a child of the business object
                      // for stored queries.
                      boQryGrp.setParentBO(boQuery);
                      boQryGrp.setParentRelation("query2qry_grp");
                      boQryGrp.setQueryUsingView(true);
                      // Query the business objects.
                      boView.query();
                      // Iterate through the rows in the view.
                      for (nRows = 1; nRows <= boView.getCount(); nRows++) {
                         for (nQryGrps = 1; nQryGrps <= boQryGrp.getCount(); nQryGrps++){
                            if (boQryGrp.getValue("title").toString().equals(
                               "Web Contact Cases")) {
                               boQryGrp.remove();
                            }
                            boQryGrp.moveNext();
                         }
                         boView.moveNext();
                      }
                      // Commit changes to the database. Since multiple rows may be
                      // affected by changes, use BO.UpdateAll instead of BO.Update.
                      boView.updateAll();
                      ...




238
                                                  CHAPTER 5   Working with Database Views




In JavaScript:
...
var BoView, BoQuery, BoQryGrp;
var nRows, nQryGrps;
...
// Set up a Generic business object for the qry_grp_owner view.
BoView = ClfyForm.CreateGenericBO("qry_grp_owner");
BoView.DataFields = "*";
// Retrieve only the stored queries owned by the current user.
BoView.Filter =
   "owner_objid = '" + ClfySession.Item("user.id") + "'";
BoView.SortOrder = "grp_title, title";
// Set up a Generic business object for stored queries.
BoQuery= ClfyForm.CreateGenericBO("query");
// Restrict fields returned to those fields available in the view.
BoQuery.DataFields = "title";
// Set up the business object as a child of the business object
// for the view.
BoQuery.ParentBO = BoView;
BoQuery.QueryUsingView = true;
// Set up a Generic business object for query groups.
BoQryGrp = ClfyForm.CreateGenericBO("qry_grp");
// Restrict fields returned to those fields available in the view.
BoQryGrp.DataFields = "title";
// Set up the business object as a child of the business object
// for stored queries.
BoQryGrp.ParentBO = BoQuery;
BoQryGrp.ParentRelation = "query2qry_grp";
BoQryGrp.QueryUsingView = true;
// Query the business objects.
BoView.Query();
// Iterate through the rows in the view.
for (nRows = 1; nRows <= BoView.Count; nRows++) {
   // Since each row in the view corresponds to one row in BoQuery,
   // we don’t need to iterate through BoQuery (only one row should
   // be exposed at a time).
   // Iterate through each query group.
   for (nQryGrps = 1; nQryGrps <= BoQryGrp.Count; nQryGrps++) {
      // If the Web Contact Cases query group is selected,
      // remove it to disassociate it from the currently selected
      // stored query. (Effectively, this removes the stored query
      // from this query group.)
      if (BoQryGrp("title").Value == "Web Contact Cases") {
         BoQryGrp.Remove();
      }
      BoQryGrp.MoveNext();
   }
   BoView.MoveNext();
}
// Commit changes to the database. Since multiple rows may be
// affected by the changes, use BO.UpdateAll instead of BO.Update.
BoView.UpdateAll();
...




                                                                                     239
Setting Up a Child Business Object Hierarchy




240
C H A P T E R      6




Working with Flexible Attributes



CONTENTS
 Overview: Flexible Attributes 242

 Guidelines for Using Flexible Attributes 242

 Getting and Setting Flexible Attribute Values 243

 Using Views with Flexible Attributes 244

 Using Flexible Attributes as Search Criteria 244

 Retrieving Templates and Flexible Attribute Definitions 245

 Creating New Templates and Flexible Attribute Definitions 248

 Calling the publish Method 248




                                                                 241
Overview: Flexible Attributes




Overview: Flexible Attributes
                   A static attribute is a field that is stored in a database. For example, an entity that defines
                   employees may have static attributes such as first name, last name, job title and department
                   number. These static attributes are fixed and are stored in a column of the database. Each time a
                   new static attribute is added, the schema must be changed.

                   Flexible attributes are fields that can be defined for an entity without changing the schema. Flexible
                   attributes can be used for objects in the same table that have different sets of attributes. For
                   example, a car can have engine size, color, and number of doors, while a computer can have CPU,
                   memory, and disk size.

                   A flexible attribute is stored in a row, in a separate table, related to the “owning” table.

                   This chapter does not discuss the many of the concepts related to flexible attributes. For
                   explanations of concepts, see the Flexible Attributes Guide.

                   In code, you can use flexible attributes in these ways:
                       You need to decide if flexible attributes are the correct solution for your application. For details,
                       read Guidelines for Using Flexible Attributes on page 242.
                       To get and set the value of a flexible attribute, you use properties and methods in the common
                       abstract class and the Field object to get and set flexible attributes. For details, see the section
                       Getting and Setting Flexible Attribute Values on page 243.
                       To use views with a flexible attribute, create a business object and attach it to the view business
                       object. For details, see the section Using Views with Flexible Attributes on page 244.
                       To specify flexible attribute values in calls to Filter and FilterRelated, you use the “@”
                       notation. For details, see the section Using Flexible Attributes as Search Criteria on page 244.
                       To get template and flexible attribute definitions, create an instance of FlexTmpl. For details,
                       see the section Retrieving Templates and Flexible Attribute Definitions on page 245.
                       To create flexible attribute templates and definitions, create an instance of FlexTmpl and then
                       and call its query method. For details, see the section Creating New Templates and Flexible
                       Attribute Definitions on page 248.




Guidelines for Using Flexible Attributes
                   Flexible attributes are not intended to serve the same purpose as modifying the schema. If an
                   attribute is going to be used by more than five to ten per cent of rows, then it should be a static
                   attribute. Flexible attributes are intended for cases where you need the flexibility provided by
                   being able to conditionally attach an attribute to a specific object.

                   Flexible attributes are slow compared to static attributes. They require an additional round trip to
                   retrieve or to determine which have been activated by templates. Flexible attributes cannot quickly
                   be searched or filtered. Flexible attributes do not reside in the base table with the rest of the row’s
                   data, so third-party tools do additional work to retrieve them.




242
                                                                          CHAPTER 6   Working with Flexible Attributes




Getting and Setting Flexible Attribute Values
         Flexible attributes are stored in a table named fa_* where the asterisk represents the object (such as
         case) for which the table holds flexible attributes. You use properties and methods in the common
         abstract class and the Field object to get and set flexible attributes.

         In Java, you call the getValue method to retrieve a flexible attribute value. The syntax is:
         MyFlexVal = BO.getFlexFields().getItem("FlexAttrName").getValue();

         You can also get a Fields collection by simply calling getFlexFields:
         Fields MyFlexVals = BO.getFlexFields();

         The getFlexFields method returns the full set of flexible attributes defined for the current
         object, not just those that are related to the current object. You can cycle through them and:
            Use those that have data.
            Use those that have been activated by a template (determined by calling Field.isActive).
            Use those that have been activated by a template or have a value (determined by calling
            Field.isEnabled).

         For more about Field.isActive and Field.isEnabled, see Template Checking on page 244.

         In Visual Basic or VBScript, you refer to the FlexFields property:
         MyFlexVal = BO.FlexFields.Item("FlexAttrName").Value

         To change the value of a flexible attribute in Java, call setValue:

         BO.getFlexFields().getItem("FlexAttrName").setValue(NewVal);

         In Visual Basic or VBScript, set the Value property:

         BO("FlexAttrName").Value = NewVal

         To delete a flexible attribute, set its value to null.

         When you add a new row for an object, create flexible attributes for it by calling getFlexFields
         or referring to the FlexFields property. This creates a new, empty flexible attribute. Then, you
         assign a value to it by calling setValue or by setting the Value property.


         Example
         Generic sitePartBO = clfyBoContext.createGenericBO("site_part");
         sitePartBO.setDataFields("*");
         sitePartBO.setFilterRelated("site_part2part_info:part_info2part_num;part_number=
         74574953483" );
         sitePartBO.query();
         sitePartBO.moveFirst();
         while( ! sitePartBO.getEOF()) {
            Fields faFields = sitePartBO.getFlexFields();
            String faName = null;
            int fldsCount = faFields.getCount();
            for(int k = 1; k <= fldsCount; k++) {
               faName = faFields.getItem(k).getName();
               if(faName.equals("equipment_no")) {
                  // Some action


                                                                                                                  243
Using Views with Flexible Attributes




                               ...
                           }
                       }
                       sitePartBO.moveNext();
                   }


                   Caching
                   The first time you retrieve a flexible attribute, its value is cached and used in later references. When
                   you call query, all cached flexible attribute data is deleted. After that, call the getFlexFields
                   method or a reference to the FlexFields property to force a retrieval from the database.


                   Template Checking
                   A template is a grouping of flexible attribute definitions and zero, one, or many definitions that
                   describe characteristics of the definition. The condition in a template determines whether the
                   template’s definitions are relevant for a specific instance of the object. An example of a condition is
                   that the quantity must be greater than ten. An object can have multiple templates.

                   Templates are checked when you call isActive or isEnabled. The isActive method returns
                   true if the flexible attribute was activated by a condition in a template and the current date is
                   between effective_start_date and effective_end_date in table_flex_defn (see
                   Table 18 on page 247). The isEnabled method returns true if the isActive method returns True
                   or the flexible attribute has a value in the database.




Using Views with Flexible Attributes
                   You cannot directly use flexible attributes with views. If you want to get flexible attributes for an
                   object in a view that is not a base object, create a business object and attach it to the view business
                   object using QueryUsingView. For details, see the section Setting Up a Business Object for a Related
                   Table on page 230. You can then get the flexible attributes from this business object.




Using Flexible Attributes as Search Criteria
                   You can search for flexible attributes in calls to Filter and FilterRelated. You use the same
                   syntax as for other values, except that you add an ampersand (“@”) at the start of the name of the
                   flexible attribute. For example, to find all products that are red (where red is a value of “color”
                   which is defined as a flexible attribute):
                       ProductBo.SetFilter( "@color=’red’" );

                   This example gets all cases with products that are red:
                       CaseBo.SetFilterRelated( "case2product;@color=’red’" );

                   If the name of a flexible attribute contains a special character (including the space character), then
                   you must enclose the name in double quotes. The entire filter string must be in quotes, so you
                   escape the quotes around the flexible attribute name:
                       CaseBo.SetFilterRelated( "case2product;\"@hair color\"=’red’" );


244
                                                                            CHAPTER 6    Working with Flexible Attributes




Retrieving Templates and Flexible Attribute Definitions
         The templates are stored in a table named table_flex_tmpl and the definitions are stored in a
         table called table_flex_defn. There is a many-to-many relation between these two tables.

         To get existing template and flexible attribute definitions, create an instance of FlexTmpl and call
         its query method. You can then use the row positioning methods such as moveFirst and
         moveNext to navigate among the rows.

         You can set up a filter specification with setFilter or setFilterRelated. For example, to get
         templates just for the Case object, set the filter to “type_id=0”.

         Call getValue to retrieve template field values. Table 17 describes the fields in a template.

         This example demonstrates the preceding.
         import com.clarify.cbo.*;
         ...
         FlexTmpl MyFlexTmpl;
         ...
         MyFlexTmpl.setDataFields( "*" );
         // Get only the templates for the Case object
         MyFlexTmpl.Filter("type_id=0");
         MyFlexTmpl.query( );
         for (int nRows = 1; nRows <= MyFlexTmpl.getCount(); nRows++) {
            String myTmplName = MyFlexTmpl.getValue("tmpl_name").toString();
            // Perform some task
            ...
            // Move to the next row
            MyFlexTmpl.moveNext();
         }


         Table 17 Template Fields
         Field Name      Data Type   Description
         type_id         Number      The object type to which the template applies. You can find the object type by
                                     checking the manual Data Dictionary for Objects.
         tmpl_name       String      The name that you use to refer to the template.
         tmpl_type       Number      The type of template:
                                     Value                                 Description
                                     cboFlexTemplateBasic                  The condition field contains a
                                                                           condition that enables the template for a
                                                                           particular row in the table.
                                     cboFlexTemplateOrigin                 The condition field contains a
                                                                           relationship path that relates table rows
                                                                           to destination templates.
                                     cboFlexTemplateDestination            The template is a destination template
                                                                           and the condition field is ignored.




                                                                                                                     245
Retrieving Templates and Flexible Attribute Definitions




                   Table 17 Template Fields (Continued)
                    Field Name          Data Type     Description
                    description         String        A description of the template.
                    condition           String        The condition that determines if this template is active. An empty condition
                                                      field means that the template is always active.
                                                      The condition can contain any number of condition expressions concatenated
                                                      with “AND”. A conditional expression contains a field, a logical operator, and a
                                                      constant:
                                                      RelationPath:Field operator value
                                                      The field can be a static or flexible attribute field. The field can optionally be
                                                      qualified with a relation path.
                                                      Logical operators include the standard operators and three string operators:
                                                      “<“, “<=”, “=”, “>=”, “>”, “!=”, “starts with”, “ends with”, and
                                                      “contains”.
                                                      For example:
                                                      fld1 > 4
                                                      @fld1 starts with 'abc' and rel1:fld2 = 4
                                                      rel1:rel2:fld1 > 42 and @fld2 contains 'xyx' and
                                                      rel1:rel3:@fld2 = 5
                                                      Note that conditional expressions with fields with relation paths and
                                                      conditional expressions with fields without relation paths can be in any order.
                                                      All those with relation paths go to FilterRelated and all those without go
                                                      to Filter.
                                                      This field is ignored if the tmpl_type is 2 (destination template).


                   Call FlexTmpl’s getAttrDefinitions method to get a FlexDefn object that contains all the
                   flexible attribute definitions for the template represented by the current row in FlexTmpl. You can
                   then browse the FlexDefn rows by calling the query method, using the row positioning methods,
                   and calling getValue to retrieve definition field values. Table 18 describes the fields in a flexible
                   attribute definition.

                   This example demonstrates the preceding:
                   import com.clarify.cbo.*;
                   ...
                   FlexTmpl MyFlexTmpl;
                   ...
                   MyFlexTmpl.setDataFields( "*" );
                   MyFlexTmpl.query( );
                   for (int nRows = 1; nRows <= MyFlexTmpl.getCount(); nRows++) {
                      FlexDefn MyFlexDefn = getAttrDefintions( );
                      for (int i = 1; i <= MyFlexDefn.getCount(); i++ ) {
                         String myValidValues =
                            MyFlexTmpl.getValue("valid_values").toString();
                         // Perform some task
                         ...
                         MyFlexDefn.moveNext();
                      }
                      MyFlexTmpl.moveNext();
                   }




246
                                                                        CHAPTER 6      Working with Flexible Attributes




Table 18 Flexible Attribute Definition Fields
Field Name                     Data Type   Description
attribute_name                 String      The name of the flexible attribute. The name does not need to be
                                           totally unique, but it must be unique among the flexible attribute
                                           definitions that relate to a given type_id in flex_tmpl. The
                                           name cannot be the same as any of the base object’s field names.
attribute_datatype             Number      The ClarifyCRM data type of the flexible attribute:
                                           Value                       Description
                                           cboDBTypeString             Character data. The string size cannot be
                                                                       larger than 256 characters.
                                           cboDBTypeInteger            Numeric integer data.
                                           cboDBTypeDateTime Date and time data.
                                           cboDBTypeDecimal            Decimal numeric data.
                                           You cannot change this value after adding a flexible attribute.
default_value                  String      The flexible attribute’s value.
description                    String      A description of the flexible attribute.
valid_values                   String      Valid values for the flexible attributes.
                                           For a flexible attribute string, valid_values hold the name of a
                                           custom choice list (in the table hgbst_lst) which holds the valid
                                           strings for this flexible attribute.
                                           For a flexible attribute date, valid_values holds a string that
                                           specifies a start and end date range in this format:
                                           ISO_DATE, ISO_DATE, ...
                                           For example:
                                           11-23-2001 22:12:23, 03-03-2002 12:02:23
                                           For a flexible attribute number, specify multiple ranges of
                                           numbers in the format:
                                           start-end,start-end, ...
                                           For example:
                                           3-4, 12-14
                                           You must supply the code that validates against this field.
is_required                    Boolean     Specifies if this is a required flexible attribute:
                                           Value                       Description
                                           0                           No
                                           1                           Yes
                                           You must supply the code that validates against this field.
effective_start_date           Date        Initial date when this definition is usable. If not set, the definition
                                           is active unless a specified effective_end_date has already
                                           passed.
effective_end_date             Date        Final date when this definition is not usable. If this is not set, the
                                           attribute stays active forever.
label                          String      Displayable label or name. The name field must be unique, but
                                           this field does not need to be unique.




                                                                                                                   247
Creating New Templates and Flexible Attribute Definitions




                   Note: You can also create an instance of FlexDefn directly to browse flexible attribute definitions.
                   However, do not change or create flexible attribute definitions in this way. Instead, make changes to
                   FlexDefn inside of a FlexTmpl object.




Creating New Templates and Flexible Attribute Definitions
                   To create a new template, you make an instance of FlexTmpl and then call AddNew. After that, you
                   set the template definition’s fields using setValue. For example, to set the type_id to set up a
                   template for Case object, you call setValue with a statement like this:
                       MyFlexTmpl.getFields().getItem("type_id").setValue(0);

                   The example above creates a template for Case objects.

                   After you have set up a template, you create flexible attribute definitions by calling
                   GetAttrDefinitions to make an instance of FlexDefn and then calling its addNew method.

                   After that, you set the flexible attribute definition’s fields using setValue.

                   You can relate an existing flexible attribute definition to a new template by adding the object ID or
                   the row to the contained business object. For example:
                   MyFlexTmpl.getAttrDefinitions().add(
                      MyOtherFlexTmpl.getAttrDefinitions())




Calling the publish Method
                   After you make changes to templates or definitions in an instance of FlexTmpl, you must call the
                   publish method. The publish method notifies other ClarifyCRM applications to refresh any
                   cached template or definition data.




248
C H A P T E R     7




Working with Expressions



CONTENTS
 Overview: Expressions 250

 Contexts 250

 Special Characters in Names 252

 Using Expressions in a Focus Object Context 253

 Using Expressions in a Database Context 254

 Using Expressions in a Database Context with Rowsets 255

 Using Expressions in a Session Context 256

 Using Expressions in an XML Document Context 257

 Using Expressions in a Map Context 260

 Using Expressions in a CDO Context 261




                                                            249
Overview: Expressions




Overview: Expressions
                 An expression is a Boolean or arithmetic expression that combines data paths (explained below)
                 using operators. The operands in an expression are either literals (numbers and strings), paths, or
                 functions. You use expressions to access:
                        The ClarifyCRM database
                        A ClarifyCRM session
                        XML data and attributes
                        Java HashMap data
                        CDO data and attributes
                        Plain old Java object (POJO) getter methods
                        Load bean attributes
                        Business object fields or flexible attributes
                        HttpServletRequest attributes or parameters

                 A data path specifies the location of an item or rowset of data in the database or in a hierarchical
                 structure of data. Data paths and expressions are a standard Clarify data access scheme.

                 The Expr business object provides a programmatic interface for processing expressions. This lets
                 you use expressions as metadata for your objects. This is an example of an expression:
                        $Focus:site2contact_role(primary_site=1):role_name = 'Trainer'

                 This chapter concentrates on showing the programmatic interface for using expressions. To
                 simplify the explanation, this chapter uses data paths in the examples. For information about the
                 complete syntax of expressions, see the System Administration Guide.




Contexts
                 A context is a starting point to access using a data path. How you use expressions with the Expr
                 business object depends on the context. Table 19 describes these types of contexts:
                        A focus object context which is a path to the database relative to an instance of a business object.
                        For details, see the section Using Expressions in a Focus Object Context on page 253.
                        A database context which is an absolute path to the database.
                        For details, see the section Using Expressions in a Database Context on page 254 and Using
                        Expressions in a Database Context with Rowsets on page 255.
                        A session context that represents a ClarifyCRM session.
                        For details, see the section Using Expressions in a Session Context on page 256.
                        An XML context that represents a Jdom element.
                        For details, see the section Using Expressions in an XML Document Context on page 257.




250
                                                                                            CHAPTER 7   Working with Expressions




                    A map context that represents hierarchical data stored in a Java HashMap.
                    For details, see the section Using Expressions in a Map Context on page 260.
                    A CDO context that represents a CDOManager.
                    For details, see the section Using Expressions in a CDO Context on page 261.

               Note: To simplify the explanation, this chapter is organized by the type of context. However, an expression
               can have more than one path in it, and each path can use a different context. Also, a context map can hold
               multiple contexts.


Table 19 Contexts
Context        Path Specifies     Object Type                     Comment                    Example
Focus object   Database field     Same type as the Field                                     $Focus:id_number
                                  business object
               Flex field         Same type as a flexible                                    $Focus:case_prod2sitepar
                                  attribute                                                  t@bandwidth
               Rowset             com.clarify.sam.FocusO                                     $Focus:case(objid =
                                  bject[]                                                    268435457):case_phone2ph
                                                                                             one_log
Database       Database field     Same type as the Field                                     $DB:site(is_default=1):n
                                  business object                                            ame
               Flex field         Same type as a flexible                                    $DB:site(is_default=1)@m
                                  attribute                                                  yFlex
               Rowset             com.clarify.sam.FocusO                                     $DB:case(objid =
                                  bject[]                                                    268435457):case_phone2ph
                                                                                             one_log
Session        Session item       java.lang.String                String value of session    $Session:user.login_name
                                                                  item
XML            Element value      java.lang.String                XML element text           $Xml:driver:name
                                                                  value
               Attribute          java.lang.String                XML element attribute $Xml:@vin
                                                                  text value
               Element            org.jdom.Element                JDom element               $Xml:address!ELEMENT
HashMap        Element            java.lang.Object                Object specified by        $Var:sa:phone
                                                                  map key




                                                                                                                            251
Special Characters in Names




Table 19 Contexts (Continued)
Context           Path Specifies     Object Type                        Comment                 Example
CDO Manager       Cdo                com.clarify.webframewo                                     $Cdo:subcaseCdo
                                     rk.Cdo
                  Cdo field          java.lang.String                                           $Cdo:subcaseCdo:title
                  Attribute
                  Cdo attribute      java.lang.String                                           $Cdo:subcaseCdo@dbObject
                  Cdo field from     If the field is an object ID or    Cdo has table or view   $Cdo:subcaseCdo:objid
                  the database       object ID of the base table in a   name
                                     view, then
                                     com.clarify.sam.FocusO
                                     bject. In all other cases, it is
                                     the same type as the Field
                                     business object.
                  Cdo field not      java.lang.String                   Cdo does not have       $Cdo:variable:myField
                  from the                                              table or view name
                  database
                  CdoCol             com.clarify.webframewo                                     $Cdo:subcaseCdoCol
                                     rk.CdoCol
                  CdoCol attribute java.lang.String                                             $Cdo:myNewCdoCol@dbObjec
                                                                                                t
POJO              Getter method      Type returned by method                                    $Object:Emp_Id
                  token
Load bean         Attribute          java.lang.String                                           $LB:CASE
Business object Field                Same type as the Field                                     $BO:id_number
                                     business object
                  Flexible attribute Same type as a flexible                                    $BO:@callerType
                                     attribute
HttpServlet-      Attribute          java.lang.String                                           $Http:@Att1
Request
                  Parameter          java.lang.String                                           $Http:Param1




Special Characters in Names
                 If the name of an item in an expression contains a special character (including the space character),
                 then you must enclose the name in double quotes. The entire expression string must be in quotes,
                 so you escape the quotes around the name:
                     String strExprValue = "$Focus!\"Contact First Name\"";




252
                                                                            CHAPTER 7   Working with Expressions




Using Expressions in a Focus Object Context
        To use expressions with a focus object context, you need to:
        1. Create a HashMap.
        2. Create the Expr object that will evaluate the expression.
        3. Create an instance of FocusObjectImpl that represents the focus object. Calling methods in
           FocusObjectImpl, you assign a name and an object ID to the focus object.
        4. Put the instance of FocusObjectImpl in the HashMap as a value, specifying the name of the
           table as the key.
        5. Call the evaluate method in the instance of Expr, passing the HashMap and the expression as
           parameters. The expression must start with “$Focus”.

        You can also use rowsets with focus objects. The expression must end in a relation and you must
        read the result of evaluation into an array of FocusObjectImpls. See Using Expressions in a
        Database Context with Rowsets on page 255.

        Import libraries for the ClarifyCRM session, the FocusObjectImpl class, the Expr class, and the
        HashMap class.
           import   com.clarify.cbo.*;
           import   com.clarify.sam.*;
           import   com.clarify.expr.*;
           import   java.util.*;

        Create a new application and session and login.
           public class MyFocusTest {
              public static void main(String[] args) {
                   Application cboApp = new Application();
                   Session cboSession = cboApp.getGlobalSession();
                   cboSession.login("sa", "freshen");

        Create the context map that will hold the focus object.
           HashMap contextMap = new HashMap();

        Create the object that will evaluate the expression.
           Expr myFocusExpression = new Expr();

        Set up the focus object’s name (“site” in this example) and ID.
           String strFocusObjectName = "site";
           String strFocusObjectID = "268435457";

        Create an instance of FocusObjectImpl to hold the focus object.
           FocusObjectImpl myFocusObj = new FocusObjectImpl();

        Assign a name and object ID to the focus object.
           myFocusObj.setObjType(strFocusObjectName);
           myFocusObj.setObjid(strFocusObjectID);

        Put the focus object in the HashMap as a value, specifying the table name (site in this example) as
        the key.
           contextMap.put("site", myFocusObj);



                                                                                                            253
Using Expressions in a Database Context




                  Set up the expression to evaluate.
                     String strFocusFieldName = "name";
                     String strExprFieldValue = "$Focus:" + strFocusFieldName;

                  Evaluate the expression.
                     String strResultFieldValue = (String) myFocusExpression.evaluate(cboSession,
                     contextMap, "", strExprFieldValue);

                  Print the results:
                             System.out.println("Expression: " + strExprFieldValue);
                             System.out.println("Result: Focus field " + strFocusFieldName +
                             " in " + strFocusObjectName + ": " + strResultFieldValue + "\n");
                         }
                     }

                  Running the code above produces this output:
                     Expression: $Focus:name
                     Result: Focus field name in site: Main Office




Using Expressions in a Database Context
                  To use expressions in an database context, you need to:
                  1. Create the Expr object that will evaluate the expression.
                  2. Call the evaluate method in the instance of Expr, passing the expression as a parameter. The
                     expression must start with “$DB”.

                  Import the libraries for the ClarifyCRM session and the Expr class.
                     import com.clarify.cbo.*;
                     import com.clarify.expr.*;

                  As in the previous example, create a new application and session and login.
                     public class MyDbTest {
                        public static void main(String[] args) {
                           Application cboApp = new Application();
                           Session cboSession = cboApp.getGlobalSession();
                           cboSession.login("sa", "freshen");

                  Create the object that will evaluate the expression.
                     Expr myDbExpression = new Expr();

                  Set up the expression to evaluate.
                     String strDbObjectName = "site";
                     String strCriteria = "(is_default = 1)";
                     String strDbFieldName = "name";
                     String strExprFieldValue = "$DB:" + strDbObjectName + strCriteria + ":" +
                     strDbFieldName;

                  Evaluate the expression. Note that null is passed for the second parameter because you do not need
                  a HashMap to evaluate an expression for a database context.
                     String strResultFieldValue = (String) myDbExpression.evaluate(cboSession, null,
                     "", strExprFieldValue);


254
                                                                              CHAPTER 7   Working with Expressions




        Print the results.
                   System.out.println("Expression: " + strExprFieldValue +"\n");
                   System.out.println("Result: DB field " + strDbFieldName + " in " +
                   strDbObjectName + ": " + strResultFieldValue + "\n");
               }
           }

        Running the code above produces this output:
           Expression: $DB:site(is_default = 1):name
           Result: DB field name in site: Main Office




Using Expressions in a Database Context with Rowsets
        If a data path ends in a relation (such as case_phone2phone_log in this example), then
        evaluating it can return more than one row.

        Note: You can also use rowsets with focus object contexts.


        To use expressions in a database context with rowsets, you need to:
        1. Create the Expr object that will evaluate the expression.
        2. Call the evaluate method in the instance of Expr, passing the expression as a parameter. The
           expression must start with “$DB”.
        3. Put the result of calling evaluate in an array of FocusObjectImpls. You then can cycle
           through the array to process each result.

        Import libraries for the ClarifyCRM session, the FocusObjectImpl class, and the Expr class.
           import com.clarify.cbo.*;
           import com.clarify.sam.*;
           import com.clarify.expr.*;

        Create a new application and session and login.
           public class MyRowsetTest {
              public static void main(String[] args) {
                 Application cboApp = new Application();
                 Session cboSession = cboApp.getGlobalSession();
                 cboSession.login("sa", "freshen");

        Create the object that will evaluate the expression.
           Expr myDbExpression = new Expr();

        Set up the expression to evaluate.
           String strDbObjectName = "case";
           String strRelationName = "case_phone2phone_log";
           String strExprFieldValue = "$DB:" + strDbObjectName + "(objid = 268435457):" +
           strRelationName;




                                                                                                              255
Using Expressions in a Session Context




                  Evaluate the expression. Note that null is passed for the second parameter because you do not need
                  a HashMap to evaluate an expression for a database context.
                      FocusObjectImpl[] myFocusObj = (FocusObjectImpl[])
                      myDbExpression.evaluate(cboSession, null, "", strExprFieldValue);

                  Print the results.
                              System.out.println("Expression: " + strExprFieldValue + "\n");
                              for( int i = 0; i < myFocusObj.length; i++) {
                                   System.out.println("Result: " + "case 268435457" + ": " +
                                   myFocusObj[i].getObjid() + " " + myFocusObj[i].getObjType() +
                                   "\n");
                              }
                          }
                      }

                  Running the code above produces this output:
                      Expression: $DB:case(objid = 268435457):case_phone2phone_log
                      Result: case 268435457: 268435457 phone_log
                      Result: case 268435457: 268435459 phone_log
                      Result: case 268435457: 268435460 phone_log
                      Result: case 268435457: 268435461 phone_log




Using Expressions in a Session Context
                  To use expressions in a session context, you need to:
                  1. Create the Expr object that will evaluate the expression.
                  2. Call the evaluate method in the instance of Expr, passing the expression as a parameter. The
                     expression must start with “$Session”.

                  For more about the Session object, see the Core Business Objects Reference Guide.

                  Import libraries for the ClarifyCRM session and the Expr class.
                      import com.clarify.cbo.*;
                      import com.clarify.expr.*;

                  Create a new application and session and login.
                      public class MySessionTest {
                         public static void main(String[] args) {
                            Application cboApp = new Application();
                            Session cboSession = cboApp.getGlobalSession();
                            cboSession.login("sa", "freshen");

                  Create the object that will evaluate the expression.
                      Expr mySessionExpression = new Expr();

                  Set up the expression to evaluate.
                      String strSessionItemName = "user.login_name";
                      String strExprFieldValue = "$Session:" + strSessionItemName + "\n";




256
                                                                           CHAPTER 7   Working with Expressions




        Evaluate the expression. Note that null is passed for the second parameter because you do not need
        a HashMap to evaluate an expression for a session context.
           String strResultFieldValue = (String) mySessionExpression.evaluate(cboSession,
           null, "", strExprFieldValue);

        Print the results.
                   System.out.println("Expression: " + strExprFieldValue);
                   System.out.println("Result: SESSION item " + strSessionItemName +
                   ": " + strResultFieldValue + "\n");
               }
           }

        Running the code above produces this output:
           Expression: $Session:user.login_name
           Result: SESSION item user.login_name: sa




Using Expressions in an XML Document Context
        To use expressions in an XML document context: you need to:
        1. Using some mechanism, read the XML document into a variable. This example uses org.Jdom
           libraries.
        2. Create a HashMap and assign the XML variable as the value and “Xml” as the key.
        3. Create the Expr object that will evaluate the expression.
        4. Call the evaluate method in the instance of Expr, passing the expression and the HashMap as
           parameters. The expression must start with “$Xml”.

        Import libraries for the ClarifyCRM session, the Expr class, the HashMap class, and java.io.*
        (used to read files).
           import   com.clarify.cbo.*;
           import   com.clarify.expr.*;
           import   java.util.*;
           import   java.io.*;

        Import libraries to read an XML file. For more about these libraries, visit http://www.jdom.org/
        docs/apidocs. Note that jdom.jar must be in the classpath.
           import org.jdom.*;
           import org.jdom.input.*;
           import org.jdom.output.*;

        Create a new application and session and login.
           public class MyXmlTest {
               public static void main(String[] args) {
                  Application cboApp = new Application();
                  Session cboSession = cboApp.getGlobalSession();
                  cboSession.login("sa", "freshen");




                                                                                                           257
Using Expressions in an XML Document Context




                 Create a string variable that holds the name of the XML file and then create an instance of
                 SAXBuilder.
                     String strFileName = "XmlPathTest.xml";
                     SAXBuilder myBuilder = new SAXBuilder();

                 Read the XML file.
                     Document myDocument = null;
                     try {
                        myDocument = myBuilder.build(new File(strFileName));
                     } catch(Exception e) {
                           System.out.println("Cannot read file " + strFileName);
                           e.printStackTrace();
                           System.exit(1);
                        }

                 Get the root element of the XML.
                     Element myRootElem = myDocument.getRootElement();

                 Create the context map and assign “Xml” as the key and the XML as the value.
                     HashMap contextMap = new HashMap();
                     contextMap.put("Xml", myRootElem);

                 Create the object that will evaluate the expression.
                     Expr myXmlExpression = new Expr();

                 Create an expression string that gets the value of an element and then evaluate the expression.
                     String strExprNode = "$Xml:make";
                     String strResultNode = (String) myXmlExpression.evaluate(cboSession,
                     contextMap, "Xml", strExprNode);

                 Prints the results.
                     System.out.println("Expression: " + strExprNode);
                     System.out.println("Result: XML node for <make>: " + strResultNode + "\n");

                 Create another expression string that gets the value of an attribute and then evaluate the
                 expression.
                     String strExprNodeAttr = "$Xml:license@state";
                     String strResultNodeAttr = (String) myXmlExpression.evaluate(cboSession,
                     contextMap, "Xml", strExprNodeAttr);

                 Print the results.
                     System.out.println("Expression: " + strExprNodeAttr);
                     System.out.println("Result: XML node attribute <license attribute=''>: " +
                     strResultNodeAttr + "\n");

                 Create another expression string that gets a node and then evaluate the expression.
                     String strExprNodeElement = "$Xml:n1!ELEMENT";
                     Element resultNodeElement = (Element) myXmlExpression.evaluate(cboSession,
                     contextMap, "Xml", strExprNodeElement);

                 Print the results, including the XML for the node.
                            System.out.println("Expression: " + strExprNodeElement);
                            System.out.println("Result: XML node element for <n1>: ");




258
                                                          CHAPTER 7   Working with Expressions




           XMLOutputter xmlOut = new XMLOutputter();
              try {
                 xmlOut.output(resultNodeElement, System.out);
              }
              catch(Exception xmlE) {
                 System.out.println("Cannot write out element");
                 xmlE.printStackTrace();
              }
       }
   }

Assume that XmlPathTest.xml contains the following.
   <?xml version="1.0" encoding="UTF-8"?>
   <car vin="123fhg5869705iop90">
      <!--Description of a car-->
      <make>Toyota</make>
      <model>Celica</model>
      <full_description>
         <number_of_doors>4</number_of_doors>
         <interior_color>beige</interior_color>
      </full_description>
      <n1>
            <n2>
               <n3>
                  <n4>
                     <n5>
                        n5 text
                     </n5>
                  </n4>
               </n3>
            </n2>
      </n1>
      <year>1997</year>
      <color>green</color>
      <license state="CA">1ABC234</license>
   </car>

Running the Java code above produces this output.
   Expression: $Xml:make
   Result: XML node for <make>: Toyota
   Expression: $Xml:license@state
   Result: XML node attribute <license attribute=''>: CA
   Expression: $Xml:n1!ELEMENT
   Result: XML node element for <n1>:
   <n1>
      <n2>
         <n3>
            <n4>
               <n5>
                  n5 text
               </n5>
            </n4>
         </n3>
      </n2>
   </n1>


                                                                                          259
Using Expressions in a Map Context




Using Expressions in a Map Context
                  To use expressions with a map context, you need to:
                  1. Create a HashMap and assigning it keys and values that represent your data.
                  2. Create another HashMap assigning the HashMap in the previous step as a value and “Var” as
                     the key.
                  3. Create the Expr object that will evaluate the expression.
                  4. Call the evaluate method in the instance of Expr, passing the expression and the HashMap as
                     parameters. The expression must start with “$Var”.

                  Import libraries for the ClarifyCRM session, the Expr class, and the HashMap.
                     import com.clarify.cbo.*;
                     import com.clarify.expr.*;
                     import java.util.*;

                  Create a new application and session and login.
                     public class MyVarTest {
                        public static void main(String[] args) {
                           Application cboApp = new Application();
                           Session cboSession = cboApp.getGlobalSession();
                           cboSession.login("sa", "freshen");

                  Create a HashMap and assign it keys and values.
                     HashMap map = new HashMap( );
                     map.put( "aKey", "aValue" );
                     map.put( "bKey", "bValue" );
                     map.put( "cKey", "cValue" );

                  Create another HashMap. Assign “Var” as its key and assign the previous HashMap as its value.
                     HashMap contextMap = new HashMap();
                     contextMap.put("Var", map);

                  Create the object that will evaluate the expression.
                     Expr myVarExpression = new Expr();

                  Set up the expression to evaluate.
                     String strVarValue = "$Var:";
                     String strVarObjectName = "bKey";
                     String strExprFieldValue = strVarValue + strVarObjectName;

                  Evaluate the expression.
                     String strResultFieldValue = (String) myVarExpression.evaluate(cboSession,
                     contextMap, "", strExprFieldValue);

                  Print the results.
                     System.out.println("Expression: " + strExprFieldValue);
                     System.out.println("Result: " + strResultFieldValue + "\n");
                     ...

                  Running the code above produces this output.
                     Expression: $Var:bKey
                     Result: bValue


260
                                                                          CHAPTER 7   Working with Expressions




Using Expressions in a CDO Context
        To use an expression with a CDO context, you need to:
        1. Create an instance of CdoManager
        2. Create instances of Cdos and CdoCols as needed.
        3. Create a HashMap and assign the CdoManager as the value and “Cdo” as the key.
        4. Create the Expr object that will evaluate the expression.
        5. Call the evaluate method in the instance of Expr, passing the expression and the HashMap as
           parameters. The expression must start with “$Cdo”.

        For more about Cdos, see the manual User Interface Framework Customization Guide.

        Import libraries for the ClarifyCRM session, the FocusObjectImpl class, the Expr class, and the
        HashMap class. Also import com.clarify.webframework.* for the Cdo, CdoCol, and
        CdoManager objects.
           import   com.clarify.cbo.*;
           import   com.clarify.sam.*;
           import   com.clarify.expr.*;
           import   com.clarify.webframework.*;
           import   java.util.*;

        Create a new application and session and login.
           public class MyCdoTest {
              public static void main(String[] args) {
                 Application cboApp = new Application();
                 Session cboSession = cboApp.getGlobalSession();
                 cboSession.login("sa", "freshen");

        Create a new CdoManager from which you can create Cdos and CdoCols.
           CdoManager myCdoManager = new com.clarify.webframework.CdoManager();

        Create a new Cdo and set the table or view name.
           String strCdoName = "myNewCdo";
           Cdo myCdo = myCdoManager.createCdo(strCdoName);
           String strCdoTableName = "site";
           myCdo.setDbObjectName(strCdoTableName);

        Set up a custom attribute for the Cdo.
           String strCdoCustomAttrName = "my_cdo_custom_attribute_name";
           String strCdoCustomAttrValue = "my_cdo_custom_attribute_value";
           myCdo.setCustomAttr(strCdoCustomAttrName, strCdoCustomAttrValue);

        Add a field to the Cdo.
           String strCdoFieldName = "name";
           String strCdoFieldValue = "my_value";
           myCdo.addField(strCdoFieldName, strCdoFieldValue);

        Add an object ID field to the Cdo.
           myCdo.addField("objid", "268435457");

        Create a new CdoCol and set the table or view name.


                                                                                                          261
Using Expressions in a CDO Context




                     String strCdoColName = "myNewCdoCol";
                     CdoCol myCdoCol = myCdoManager.createCdoCol(strCdoColName);
                     String strCdoColTableName = "contact";
                     myCdoCol.setDbObjectName(strCdoColTableName);

                 Set a custom attribute for the CdoCol.
                     String strCdoColCustomAttrName = "my_cdocol_custom_attribute_name";
                     String strCdoColCustomAttrValue = "my_cdocol_custom_attribute_value";
                     myCdoCol.setCustomAttr(strCdoColCustomAttrName, strCdoColCustomAttrValue);

                 Create a HashMap, assign “Cdo” as the key and the CdoManager as the value.
                     HashMap contextMap = new HashMap();
                     contextMap.put("Cdo", myCdoManager);

                 Create the Expr object for the Cdo.
                     Expr myCdoExpression = new Expr();

                 Create an expression for the Cdo.
                     String strExprFieldValue = "$Cdo:" + strCdoName + ":" + strCdoFieldName;

                 Evaluate the expression.
                     String strResultFieldValue = (String) myCdoExpression.evaluate(cboSession,
                     contextMap, "Cdo", strExprFieldValue);

                 Print the results.
                     System.out.println("Expression: " + strExprFieldValue);
                     System.out.println("Result: Cdo field " + strCdoFieldName + " in " + strCdoName
                     + ": " + strResultFieldValue + "\n");

                 Create an expression for the object ID field in the Cdo, evaluate it, and print the results.
                     String strExprObjid = "$Cdo:" + strCdoName + ":objid";
                     FocusObjectImpl resultFO = (FocusObjectImpl)
                     myCdoExpression.evaluate(cboSession, contextMap, "Cdo", strExprObjid);
                     System.out.println("Expression: " + strExprObjid);
                     System.out.println("Focus object for objid field from " + strCdoName + " --
                     ObjType: " + resultFO.getObjType() + ", Objid: " + resultFO.getObjid() + "\n");

                 Create an expression for the dbObject attribute of the Cdo, evaluate it, and print the results.
                     String strExprAttrValue = "$Cdo:" + strCdoName + "@dbObject";
                     String strResultAttrValue = (String) myCdoExpression.evaluate(cboSession,
                     contextMap, "Cdo", strExprAttrValue);
                     System.out.println("Expression: " + strExprAttrValue);
                     System.out.println("Cdo attribute dbObject in " + strCdoName + ": " +
                     strResultAttrValue + "\n");

                 Create an expression for a custom attribute of the Cdo, evaluate it, and print the results.
                     String strExprCustomAttrValue = "$Cdo:" + strCdoName + "@" +
                     strCdoCustomAttrName;
                     String strResultCustomAttrValue = (String) myCdoExpression.evaluate(cboSession,
                     contextMap, "Cdo", strExprCustomAttrValue);
                     System.out.println("Expression: " + strExprCustomAttrValue);
                     System.out.println("Cdo attribute " + strCdoCustomAttrName + " in " + strCdoName
                     + ": " + strResultCustomAttrValue + "\n");


262
                                                                     CHAPTER 7   Working with Expressions




Create an Expr object for the CdoCol:
   Expr myCdoColExpression = new Expr();

Create an expression for the CdoCol, evaluate it, and print the results.
   String strExprAttrValueCdoCol = "$Cdo:" + strCdoColName + "@dbObject";
   String strResultAttrValueCdoCol = (String)
   myCdoColExpression.evaluate(cboSession, contextMap, "Cdo",
   strExprAttrValueCdoCol);
   System.out.println("Expression: " + strExprAttrValueCdoCol);
   System.out.println("CdoCol attribute dbObject in " + strCdoColName + ": " +
   strResultAttrValueCdoCol + "\n");

Create an expression for an attribute of the CdoCol, evaluate it, and print the results.
           String strExprCustomAttrValueCdoCol = "$Cdo:" + strCdoColName +
           "@" + strCdoColCustomAttrName;
           String strResultCustomAttrValueCdoCol =
           (String) myCdoColExpression.evaluate(cboSession,
           contextMap, "Cdo", strExprCustomAttrValueCdoCol);
           System.out.println("Expression: " + strExprCustomAttrValueCdoCol);
           System.out.println("CdoCol attribute " + strCdoColCustomAttrName +
           " in " + strCdoColName + ": " + strResultCustomAttrValueCdoCol +
           "\n");
       }
   }

Running the code above produces this result:
   Expression: $Cdo:myNewCdo:name
   Result: Cdo field name in myNewCdo: my_value
   Expression: $Cdo:myNewCdo:objid
   Focus object for objid field from myNewCdo -- ObjType: site, Objid: 268435457
   Expression: $Cdo:myNewCdo@dbObject
   Cdo attribute dbObject in myNewCdo: site
   Expression: $Cdo:myNewCdo@my_cdo_custom_attribute_name
   Cdo attribute my_cdo_custom_attribute_name in myNewCdo:
   my_cdo_custom_attribute_value
   Expression: $Cdo:myNewCdoCol@dbObject
   CdoCol attribute dbObject in myNewCdoCol: contact
   Expression: $Cdo:myNewCdoCol@my_cdocol_custom_attribute_name
   CdoCol attribute my_cdocol_custom_attribute_name in myNewCdoCol:
   my_cdocol_custom_attribute_value




                                                                                                     263
Using Expressions in a CDO Context




264
P A R T    I I




Building Your Application



CONTENTS
 CHAPTER 8, Setting Up Your Application 267

 CHAPTER 9, Using Global Transactions and Bulk Queries 313

 CHAPTER 10, Handling Errors 327

 CHAPTER 11, Writing Event Handlers 339

 CHAPTER 12, Using RemoteCB 353




                                                             265
266
C H A P T E R      8




Setting Up Your Application



CONTENTS
 Overview: Using CBO in an Application 268

 Creating and Initializing the Application Object 269

 Getting the Session Object 270

 Setting the Locale and Time Zone 272

 Logging In to the Database 290

 Getting the Context for the Business Objects 296

 Creating Business Objects 296

 Getting Configuration and Profile Information 297

 Getting Diagnostic Information on CBO 298

 Ending the CBO Session 298

 Working in Different Programming Languages 298

 Writing Web Applications with ClarifyCRM Business Objects 307




                                                                 267
Overview: Using CBO in an Application




Overview: Using CBO in an Application
                  In order to use CBO, you need to set up some objects that provide the infrastructure for the
                  business objects. These infrastructure objects include:
                      the Application object, which represents an attachment from a specific process on a machine
                      to a ClarifyCRM database
                      the Session object, which represents an authenticated connection to the database
                      the BoContext object (in Java) and the FormContext object (in ActiveX), which provide the
                      ability to programmatically create business objects and the context for the business objects

                  Note: Active Server Pages and JavaServer Pages also have their own Application object and Session
                  object. To avoid confusion, the terms ASP Application object and ASP Session object are used to refer
                  to the ASP objects. For JSP objects, the terms JSP application object and JSP session object (or
                  ServletContext object and HttpSession object) are used.


                  The following steps outline the general instructions for programmatically creating business objects.
                  Depending on your development environment and the programming language that you are using,
                  you should also read the following sections for more environment-specific information:
                      Working with Business Objects in Java on page 299
                      Working with Business Objects in JavaScript on page 303
                      Working with Business Objects in Visual Basic on page 303 (if you have dropped CBOs as controls
                      on a Visual Basic form, see Working with Dropped Controls in Visual Basic on page 303)

                  To use CBO in your application:
                  1. Create an Application object, which loads the libraries for the business objects in memory. If
                     you are writing a web application (using JavaServer Pages or Active Server Pages with VBScript
                     or JavaScript), you also need to initialize the Application object.
                      See Creating and Initializing the Application Object on page 269 for details.
                  2. Use the Application object to do one of the following:
                      – Create a Session object (if writing a web application).
                      – Get the global Session object (if writing a traditional, forms-based application).
                      See Getting the Session Object on page 270 for details.
                  3. If you are writing a web application (or if the application is not running on the user’s machine),
                     get the locale and time zone of the user’s machine and use the Session object to set the locale
                     and time zone for the session.
                      See Setting the Locale and Time Zone on page 272 for details.
                  4. Use the Session object to log in to the ClarifyCRM database.
                      See Logging In to the Database on page 290 for details.
                  5. Use the Session object to create a BoContext/FormContext object.
                      See Getting the Context for the Business Objects on page 296 for details.
                  6. Use the BoContext/FormContext object to create CBOs.
                      See Creating Business Objects on page 296 for details.


268
                                                                             CHAPTER 8   Setting Up Your Application




         You can also use the Session object for other operations, such as getting data about the logged-in
         user. For details, see Getting Configuration and Profile Information on page 297.

         The final sections of this chapter provide examples of setting up the infrastructure objects in Visual
         Basic, in ASP, and in JSP.




Creating and Initializing the Application Object
         The Application object represents an attachment to the database. Creating the Application
         object loads the libraries for CBO in memory. For a given application, you should create only one
         Application object.

         Create the Application object using one of the following ways:
            In Java, use the constructor for the Application class to create a new Application object:
            import com.clarify.cbo.*;
            ...
            Application clfyApp = new Application();
            In ActiveX (for example, in Visual Basic, JavaScript, or VBScript), use the CreateObject
            statement, and specify Clarify.CBO.App.1 as the programmatic ID.
            For example, in Visual Basic, you can use the following lines of code:
            Dim ClfyApp
            Set ClfyApp = CreateObject("Clarify.CBO.App.1")
            In JavaScript, you can use the CreateObject statement, or you can construct a new
            ActiveXObject. For example:
            var ClfyApp
            ClfyApp = new ActiveXObject("Clarify.CBO.App.1");

         If you are developing a web application, you must ensure that only one Application object is
         created and that only one request creates and initializes the object. For more information, see
         Creating the Application Object in a Java Web Application on page 307 and Creating the Application
         Object in ASP on page 312.




                                                                                                                269
Getting the Session Object




Getting the Session Object
                  The ClarifyCRM Session object represents a user’s session and database connection. In your
                  application code, you need to use the Session object to log in to the database. After logging in,
                  you can use the Session object to do the following:
                      Access data about the current user
                      Specify preferences and settings for the user, such as the user’s locale and time zone (for use
                      when formatting strings and working with date-time values)
                      Get the BoContext/FormContext object for creating CBOs
                      Format non-string values and date-time values in terms of the end user’s locale and time zone

                  The Session object also provides access to cached metadata, including cached strings from the
                  string_db database table and user choice lists.

                  The following sections explain how to access the Session object and keep track of the current
                  session:
                      Tracking the User’s Session on page 270
                      Getting the Global Session on page 270
                      Creating a New Session on page 271
                      Tracking a Session with the Session ID on page 272


                  Tracking the User’s Session
                  When you use the Session object to log in to the database, the CBO Data Service internally creates
                  a session for that user. This session persists until the Session object goes out of scope. (In Java,
                  you can also end the session by invoking the Session.release method. For information on how
                  getting the session ID affects the release method, see Tracking a Session with the Session ID on
                  page 272.)

                  To prevent the session from ending unintentionally when the Session object goes out of scope,
                  make sure that the variable for the Session object is declared with the appropriate scope. For
                  example, if you are writing a CBO application in Visual Basic and you need to use the business
                  objects in multiple procedures, declare the Session object as a public variable in your module.

                  If you are writing a web application in Java, you can keep a reference to the Session object in the
                  HttpSession object. (For details, see the section Logging In and Tracking the User’s Session on
                  page 308.) When the user is done with the CBO session, you can end the session by invoking the
                  Session.release method.


                  Getting the Global Session
                  A traditional, forms-based application (such as a command-line application or a Visual Basic
                  application) is typically run by a single user at a time. As a result, there is a single, global Session
                  object for the entire application.




270
                                                                   CHAPTER 8   Setting Up Your Application




To get the global session for the application, get the Application.GlobalSession property. For
example, the following statements create a new Application object and get the global Session
object for the application:
   In Java:
   import com.clarify.cbo.*;
   ...
   Application clfyApp = new Application();
   Session clfySession = clfyApp.getGlobalSession();
   In Visual Basic:
   Dim ClfyApp, ClfySession
   Set ClfyApp = CreateObject("Clarify.CBO.App.1")
   Set ClfySession = ClfyApp.GlobalSession


Creating a New Session
In some situations, you may need to use multiple sessions in an application. For example, a web
application (using servlets and JavaServer Pages or using Active Server Pages) can be used by
multiple users at the same time. Each user has a separate login and session with the database.

Each user session is represented by a CBO Session object. For each new user session, you must
create a new CBO Session object by invoking the Application.CreateSession method. For
example:
   In Java:
   import com.clarify.cbo.*;
   ...
   Session clfySession = clfyApp.createSession();
   In JavaScript:
   var objClfySession = ClfyApp.CreateSession();

Once you create a CBO Session object, you can use this object to set the locale and time zone for
the session and to log in to the database.

A Session object is thread-safe and can be shared by more than one thread.

Important: While Sessions are thread safe, BoContext/FormContext objects and business objects
are not. Each thread needs to create its own BoContext/FormContext and business objects and release
them at the end of the thread.


Sessions created by CreateSession time out after a period of inactivity. This period is
determined by the HttpSession timeout set by the J2EE server.

To end the user’s session, call the Session.Logout method. Logout does not release the
Session object. Another thread still can use the same Session. To end the session, you must call
Session.Release.

A Session object lives until it goes out of scope and is claimed by the garbage collector or you call
Session.Release.




                                                                                                      271
Setting the Locale and Time Zone




                  Tracking a Session with the Session ID
                  If you are developing a web application in ASP, or if you are developing a Java web application
                  that does not keep session state on a single JSP server (see Tracking the CBO Session Across Different
                  JSP Servers on page 309), you can use the session ID to keep track of the user’s session.

                  The session ID represents the state of a complete session. After you get the session ID from the CBO
                  Session object, you can release the CBO Session object from memory without ending the
                  current user’s session. The session state is serialized in the session ID.

                  You can then use the session ID with a newly created CBO Session object to “reestablish” the
                  existing user’s session. (As a result, you do not need to keep the CBO Session object persistent in
                  order to keep track of the user’s session.) In other words, the application is stateless.

                  To get the session ID for the current session, use the Session.SessionId property. To use the
                  session ID to reestablish an existing session, call Application.CreateSessionFromId,
                  specifying the saved session ID as the parameter.

                  When you call CreateSessionFromId, ClarifyCRM checks whether a timeout was specified as a
                  parameter when the Session called Login. If the Session has been inactive longer the specified
                  timeout, you get an error when you call CreateSessionFromId. If the timeout parameter was
                  not specified by the Login method, ClarifyCRM checks the db_sessiontimeout value in
                  clarify.env. Again, if the Session has been inactive longer the specified timeout, you get an
                  error when you call CreateSessionFromId. In other words, a timeout specified as a Login
                  parameter overrides the db_sessiontimeout in clarify.env.

                  The session tag is the part of the session ID that uniquely identifies a session. To get the session tag,
                  use the Session.SessionTag property.

                  Note: In Java, the Session.release method does not end the session if you got the session ID. In order
                  to end the session, you must call the Session.logout method. After calling logout, you need to
                  explicitly call the Session.release method to free objects and other resources.


                  For examples of using the session ID to track a user’s session, see Tracking the CBO Session Across
                  Different JSP Servers on page 309.




Setting the Locale and Time Zone
                  In a web application, the web browser runs on a separate machine from the web application code.
                  The machine running the business objects (such as the web server) and the end user’s machine may
                  have different locale settings and different time zones. (For a complete discussion of this issue, see
                  Working with Field Values and Data Types on page 92.)

                  To allow CBO to account for these differences, you should get the end user’s locale and time zone
                  and set those values in the Session object. You can do this either before or after logging in to the
                  database.




272
                                                                             CHAPTER 8   Setting Up Your Application




The following sections provide more information on setting the locale and time zone for the end
user’s session:
   How CBO Uses Locale and Time Zone Information on page 273
   Setting Up Locales and Time Zones in the Database on page 273
   Setting the Locale of the End User on page 274
   Setting the Time Zone of the End User on page 282
   Example of Setting the Locale and Time Zone in ASP on page 287

Note: If you are writing an application that runs on the end user’s machine (such as a Visual Basic
application), you do not need to explicitly set the locale and time zone of the end user. In this case, there is no
difference between the end user’s locale and time zone and the locale and time zone of the machine running
the application.



How CBO Uses Locale and Time Zone Information
The Session and Field objects have methods that get and parse the string representation of
date-time values, currency data, and other non-string values. The methods use the locale specified
in the Session object to determine the locale-specific formats to use in the string representation.
Similarly, the methods use the time zone specified in the Session object to account for the
difference in time zones between the end user’s machine and the machine running the application
code. (For more information, see Working with Field Values and Data Types on page 92 for details.)

In a web application, the end user’s machine (where the string representations are displayed or
entered) may have a different locale and time zone from the web server machine. In order to ensure
that string representations are formatted and parsed correctly, you must get the end user’s locale
and time zone and set those values in the Session object.


Setting Up Locales and Time Zones in the Database
By default, the ClarifyCRM database contains information on a selected number of locales and time
zones.
   The locale table stores information on locales.
   The time_zone table stores information about time zones. Information about the daylight
   savings times associated with time zones is stored in the daylight_hr table.

If you want to include additional locales and time zones, use the data exchange utility to import
these locales and time zones from the .dat files provided with the ClarifyCRM server software
(the files are located in the dbadmin/countries subdirectory):
   Additional locales are specified in the locale.dat file.
   Additional time zones are specified in the .dat files named after the continents.

For information on using the data exchange utility, see the manual System Administration Guide.




                                                                                                                273
Setting the Locale and Time Zone




                  After you import the locales and time zones, if any CBO applications are running, you must update
                  the time stamps in the configuration items named time stamp of locale, time stamp of
                  time zone, and time stamp of application lists. (The ClarifyCRM Business Objects
                  and the CBO Data Service cache locale and time zone information. You need to update these
                  configuration items to indicate that this metadata has changed and needs to be recached. For
                  details, see the System Administration Guide.)


                  Setting the Locale of the End User
                  In order to set the end user’s locale in the ClarifyCRM Session object, you need the locale code in
                  the following format:
                  aa-bb

                  where aa is the two-letter ISO 639 language code and bb is the two-letter ISO 3166 country code.
                  For example:
                  en-us
                  de-de
                  fr-fr
                  ja-jp

                  The following sections explain how to get the end user’s current locale code and how to set that
                  locale in the ClarifyCRM Session object:
                      Getting the Locale from the Web Browser on page 274
                      Getting the List of Locales in the Database on page 275
                      Setting the Locale on page 278

                  Getting the Locale from the Web Browser
                  If you are writing a web application, you can get the end user’s locale from the HTTP
                  Accept-Language header sent by the browser. When an end user sets a preferred language in the
                  browser, the browser sends the corresponding locale code in the HTTP Accept-Language
                  header.
                      In JSP, you can get the value of this header by using the request.getHeader method. For
                      example:
                      String strLocaleCode = (String) request.getHeader("Accept-Language");
                      In ASP, you can get the value of this header from the
                      Request.ServerVariables("HTTP_ACCEPT_LANGUAGE") variable. For example:
                      var strLocale = Request.ServerVariables("HTTP_ACCEPT_LANGUAGE");

                  Some browsers, such as Netscape Navigator, provide the language code but not the country code
                  (for example, en or fr). In this type of situation, you need to prompt the end user for the
                  appropriate country code. For more information, see Setting the Locale on page 278.




274
                                                                    CHAPTER 8   Setting Up Your Application




Getting the List of Locales in the Database
If you want to present the end user with a list of locales to select from, you can call the
Session.GetLocaleNameList method. This method returns a list of locales in the locale
table in the database. Use the following syntax for this method:
   Session.GetLocaleNameList(Flags)

For the Flags argument, you can specify the types of locale information that you want returned.
For more about the nFlags parameter, see the Core Business Objects Reference Guide.

Note the following guidelines:
   If you are using Java, you can specify these flags by using the static fields defined in the
   com.clarify.cbo.CboConstants class (for example,
   CboConstants.cboLocaleShowCountry).
   If you pass 0 for the Flags argument, the method gets only the country name of each locale.
   If you specify the flags to retrieve all locale information, the method returns a comma-delimited
   list of locales in the following format:
   locale_code language country, <next_locale>

For example, suppose you execute the following statements:
   In Java:
   import com.clarify.cbo.*;
   ...
   Session clfySession;
   ...
   String strLocaleList =
      clfySession.getLocaleNameList(
      CboConstants.cboLocaleShowLocaleCode |
      CboConstants.cboLocaleShowLanguage |
      CboConstants.cboLocaleShowCountry);
   In JavaScript:
      var cboLocaleShowLocaleCode = 2;
      var cboLocaleShowLanguage = 4;
      var cboLocaleShowCountry = 1;
   ...
   var strLocaleList =
      ClfySession.GetLocaleNameList(
      cboLocaleShowLocaleCode |
      cboLocaleShowLanguage |
      cboLocaleShowCountry);

After you execute these statements, strLocaleList is set to a string in the following format:
   de-de German Germany,en-us English UnitedStates,fr-fr French France,ja-jp
   Japanese Japan




                                                                                                       275
Setting the Locale and Time Zone




                  The following examples demonstrate how to use the Session.GetLocaleNameList method to
                  create an HTML SELECT element with which the end user can select the current locale:
                  <SELECT NAME="locale">
                     <OPTION
                     VALUE="en-us">English (United States)</OPTION>
                     <OPTION
                     VALUE="fr-fr">French (France)</OPTION>
                     <OPTION
                     VALUE="de-de">German (Germany)</OPTION>
                     <OPTION
                     VALUE="ja-jp">Japanese (Japan)</OPTION>
                  </SELECT>

                  This example provides the end user with a selection of all locales in the database. Each option in
                  the SELECT element is displayed in the language (country) format. The value submitted by this
                  form element is the locale code for the selected locale. In the page that processes the submitted
                  form, you can use the value of the locale element to set the locale for the current session.
                      In Java (JSP):
                      <%@ page import="com.clarify.cbo.*, java.util.*" %>
                      Session ClfySession;
                      ...
                      <SELECT NAME="locale">
                      <%
                      int nKey = 0;
                      String strLocaleCodeItem, strLanguageItem, strCountryItem;

                      // Get the current locale code.
                      strLocaleCode = (String) ClfySession.getItem("Locale.Name");

                      // Get the list of locale codes.
                      String strLocaleCodeList = ClfySession.getLocaleNameList(
                         CboConstants.cboLocaleShowLocaleCode |
                         CboConstants.cboLocaleShowAllItems);

                      // Get the list of languages.
                      String strLanguageList = ClfySession.getLocaleNameList(
                         CboConstants.cboLocaleShowLanguage |
                         CboConstants.cboLocaleShowAllItems);

                      // Get the list of countries.
                      String strCountryList = ClfySession.getLocaleNameList(
                         CboConstants.cboLocaleShowCountry |
                         CboConstants.cboLocaleShowAllItems);

                      // Convert the lists to arrays.
                      StringTokenizer st = new StringTokenizer(strLocaleCodeList, ",");
                      String[] arrayLocale = new String[st.countTokens()];
                      while (st.hasMoreTokens()) {
                         arrayLocale[nKey] = st.nextToken();
                         nKey++;
                      }

                      st = new StringTokenizer(strLanguageList, ",");
                      String[] arrayLanguage = new String[st.countTokens()];
                      nKey = 0;


276
                                                   CHAPTER 8   Setting Up Your Application




while (st.hasMoreTokens()) {
   arrayLanguage[nKey] = st.nextToken();
   nKey++;
}

st = new StringTokenizer(strCountryList, ",");
String[] arrayCountry = new String[st.countTokens()];
nKey = 0;
while (st.hasMoreTokens()) {
   arrayCountry[nKey] = st.nextToken();
   nKey++;
}

// Create an OPTION tag for each locale.
for (nKey = 0; nKey < arrayLocale.length; nKey++) {
   strLocaleCodeItem = arrayLocale[nKey];
   strLanguageItem = arrayLanguage[nKey];
   strCountryItem = arrayCountry[nKey];
   strLocaleCodeItem = strLocaleCodeItem.trim();
   strLanguageItem = strLanguageItem.trim();
   strCountryItem = strCountryItem.trim();
   %>
   <OPTION
   <%
   // Make the current locale selected by default.
   if (strLocaleCode.equals(strLocaleCodeItem)) {
      %>
      SELECTED
      <%
   }
   %>
   VALUE="<%=strLocaleCodeItem%>"><%=strLanguageItem%>
      (<%=strCountryItem%>)</OPTION>
   <%
}
%>
</SELECT>
In JavaScript (ASP):
<SELECT NAME="locale">
<%
// Get the list of locale codes.
var strLocaleCodeList =
   ClfySession.GetLocaleNameList(cboLocaleShowLocaleCode |
   cboLocaleShowAllItems);

// Get the list of languages.
var strLanguageList =
   ClfySession.GetLocaleNameList(cboLocaleShowLanguage |
   cboLocaleShowAllItems);

// Get the list of countries.
var strCountryList =
   ClfySession.GetLocaleNameList(cboLocaleShowCountry |
   cboLocaleShowAllItems);

// Remove any extra spaces between commas.


                                                                                      277
Setting the Locale and Time Zone




                      re = /\s*,\s*/g;
                      strLocaleCodeList = strLocaleCodeList.replace(re, ",");
                      strLanguageList = strLanguageList.replace(re, ",");
                      strCountryList = strCountryList.replace(re, ",");

                      // Remove any spaces in the list of locale codes.
                      re = /\s/g;
                      strLocaleCodeList = strLocaleCodeList.replace(re, "");

                      // Remove any leading and trailing spaces.
                      re = /^\s*(\S*)\s*$/g;
                      strLocaleCodeList = strLocaleCodeList.replace(re, "$1");
                      strLanguageList = strLanguageList.replace(re, "$1");
                      strCountryList = strCountryList.replace(re, "$1");

                      // Convert the lists to arrays.
                      var arrayLocale = strLocaleCodeList.split(",");
                      var arrayLanguage = strLanguageList.split(",");
                      var arrayCountry = strCountryList.split(",");

                      // Create an OPTION tag for each locale.
                      for (var nKey in arrayLocale) {
                         strLocaleCodeItem = arrayLocale[nKey];
                         strLanguageItem = arrayLanguage[nKey];
                         strCountryItem = arrayCountry[nKey];

                         // Create an option for this locale code.
                         %>
                         <OPTION
                         VALUE="<%=strLocaleCodeItem%>"><%=strLanguageItem%>
                            (<%=strCountryItem%>)</OPTION>
                         <%
                      }
                      %>
                      </SELECT>

                  Setting the Locale
                  To set the locale in the ClarifyCRM Session object, use the Session.SetLocale method. The
                  locale code that you specify must correspond to one of the rows in the locale table in the
                  ClarifyCRM database. For example:
                      In Java:
                      import com.clarify.cbo.*;
                      ...
                      Session clfySession;
                      ...
                      ClfySession.setLocale(strLocaleCode, strTimeZone);
                      In JavaScript:
                      ClfySession.SetLocale(strLocaleCode, strTimeZone);




278
                                                                       CHAPTER 8   Setting Up Your Application




If you are getting the locale from the web browser’s HTTP Accept-Language header, note that
some browsers (such as Netscape Navigator) provide the language code but not the country code
(for example, en or fr). In this type of situation, you can do the following:
1. Use the Session.SetLocale method to set the partial locale code in the ClarifyCRM
   Session object. (If the locale code does not include both the language and country, the
   Session.SetLocale method returns False.)
2. call the Session.GetLocaleNameList method to get a list of the locales for that language.
   If you use the Session.SetLocale method to set a locale code that specifies only the
   language, the Session.GetLocaleNameList method returns only the locales for that
   language (unless you specify the cboLocaleShowAllItems flag).
3. Use the resulting list to prompt the end user to select a locale.

The following example demonstrates how to use the locale code sent by the web browser and the
Session.SetLocale method to set the locale for the current session. If the browser sends an
incomplete locale code, the page uses the Session.GetLocaleNameList method to present the
end user with a list of locales for a given language (using an HTML SELECT element). Each option
in the SELECT element is displayed in the language (country) format:
<FORM ACTION="SetCompleteLocale.jsp">
<SELECT NAME="locale">
   <OPTION
   VALUE="en-au">English (Australia)</OPTION>
   ...
   <OPTION
   VALUE="en-gb">English (United Kingdom)</OPTION>
   <OPTION
   VALUE="en-us">English (United States)</OPTION>
</SELECT>
<INPUT TYPE=SUBMIT VALUE="Set Locale">
</FORM>

The value submitted by this form element is the locale code for the selected locale. In the page that
processes the submitted form, you can use the value of the locale element to set the locale for the
current session.
   In Java (JSP):
   <%@ page import="com.clarify.cbo.*, java.util.*" %>
   ...
   <% String strTimeZone = null;
   // Get the time zone of the end user (not shown in this example).
   ...
   // Get the locale from the browser.
   if (request.getHeader("Accept-Language") != null){
      strLocaleCode = (String) request.getHeader("Accept-Language");
   }
   // Attempt to set the locale.
   if (ClfySession.setLocale(strLocaleCode, strTimeZone)){
      // The locale is set, so you can direct the user to the next page.
      response.sendRedirect("NextPage.jsp");
   }
   // Otherwise, create a form to allow the user to select the locale.
   %> ...
   <FORM ACTION="SetCompleteLocale.jsp">
   <SELECT NAME="locale">


                                                                                                          279
Setting the Locale and Time Zone




                      <%
                      // Get the list of locale codes matching the partial locale code.
                         strLocaleCodeList = (String) ClfySession.getLocaleNameList(
                            CboConstants.cboLocaleShowLocaleCode);

                      // Get the list of languages matching the partial locale code.
                      strLanguageList = (String) ClfySession.getLocaleNameList(
                         CboConstants.cboLocaleShowLanguage);

                      // Get the list of countries matching the partial locale code.
                      strCountryList = (String) ClfySession.getLocaleNameList(
                         CboConstants.cboLocaleShowCountry);

                      // Convert the lists to arrays.
                      StringTokenizer st = new StringTokenizer(strLocaleCodeList, ",");
                      arrayLocale = new String[st.countTokens()];
                      while (st.hasMoreTokens()) {
                         arrayLocale[nKey] = st.nextToken();
                         nKey++;
                      }

                      nKey = 0;
                      st = new StringTokenizer(strLanguageList, ",");
                      arrayLanguage = new String[st.countTokens()];
                      while (st.hasMoreTokens()) {
                         arrayLanguage[nKey] = st.nextToken();
                         nKey++;
                      }

                      nKey = 0;
                      st = new StringTokenizer(strCountryList, ",");
                      arrayCountry = new String[st.countTokens()];
                      while (st.hasMoreTokens()) {
                         arrayCountry[nKey] = st.nextToken();
                         nKey++;
                      }

                      // Create an OPTION tag for each locale.
                      for (nKey = 0; nKey < arrayLocale.length; nKey++) {
                         strLocaleCodeItem = arrayLocale[nKey];
                         strLanguageItem = arrayLanguage[nKey];
                         strCountryItem = arrayCountry[nKey];
                         strLocaleCodeItem = strLocaleCodeItem.trim();
                         strLanguageItem = strLanguageItem.trim();
                         strCountryItem = strCountryItem.trim();
                         %>
                         <OPTION
                         <%
                         // Make the current locale selected by default.
                         if (strLocaleCode.equals(strLocaleCodeItem)) {
                            %>
                            SELECTED
                            <%
                         }
                         %>
                         VALUE="<%=strLocaleCodeItem%>"><%=strLanguageItem%>
                         (<%=strCountryItem%>)</OPTION>


280
                                                   CHAPTER 8   Setting Up Your Application




   <%
}
%>
</SELECT>
<INPUT TYPE=SUBMIT VALUE="Set Locale">
</FORM>
In JavaScript (ASP):
// Get the time zone of the end user (not shown in this example).
var strTimezone;
...
// Get the locale from the browser.
var strLocaleCode =
   Request.ServerVariables("HTTP_ACCEPT_LANGUAGE");
// Attempt to set the locale.
if (ClfySession.SetLocale(strLocaleCode, strTimezone)) {
   // The locale is set, so you can direct the user to the next page.
   Response.Redirect("NextPage.asp");
}
// Otherwise, create a form to allow the user to select the locale.
%>
...
<FORM ACTION="SetCompleteLocale.asp">
<SELECT NAME="locale">
<%
// Get the list of locale codes matching the partial locale code.
var strLocaleCodeList =
   ClfySession.GetLocaleNameList(cboLocaleShowLocaleCode);

// Get the list of languages matching the partial locale code.
var strLanguageList =
   ClfySession.GetLocaleNameList(cboLocaleShowLanguage);

// Get the list of countries matching the partial locale code.
var strCountryList =
   ClfySession.GetLocaleNameList(cboLocaleShowCountry);

// Remove any extra spaces between commas.
re = /\s*,\s*/g;
strLocaleCodeList = strLocaleCodeList.replace(re, ",");
strLanguageList = strLanguageList.replace(re, ",");
strCountryList = strCountryList.replace(re, ",");

// Remove any spaces in the list of locale codes.
re = /\s/g;
strLocaleCodeList = strLocaleCodeList.replace(re, "");

// Remove any leading and trailing spaces.
re = /^\s*(\S*)\s*$/g;
strLocaleCodeList = strLocaleCodeList.replace(re, "$1");
strLanguageList = strLanguageList.replace(re, "$1");
strCountryList = strCountryList.replace(re, "$1");

// Convert the lists to arrays.
var arrayLocale = strLocaleCodeList.split(",");
var arrayLanguage = strLanguageList.split(",");
var arrayCountry = strCountryList.split(",");


                                                                                      281
Setting the Locale and Time Zone




                      // Create an OPTION tag for each locale.
                      for (var nKey in arrayLocale) {
                         strLocaleCodeItem = arrayLocale[nKey];
                         strLanguageItem = arrayLanguage[nKey];
                         strCountryItem = arrayCountry[nKey];

                         %>
                         <OPTION
                         VALUE="<%=strLocaleCodeItem%>"><%=strLanguageItem%>
                         (<%=strCountryItem%>)</OPTION>
                         <%
                      }
                      %>
                      </SELECT>
                      <INPUT TYPE=SUBMIT VALUE="Set Locale">
                      </FORM>
                      ...


                  Setting the Time Zone of the End User
                  In order to set the end user’s time zone in the ClarifyCRM Session object, you need to get either
                  the name of the time zone or the number of hours relative to GMT.

                  The following sections explain how to get the end user’s current time zone and how to set that time
                  zone in the ClarifyCRM Session object:
                      Getting the List of Time Zones in the Database on page 282
                      Setting the Time Zone on page 284

                  Getting the List of Time Zones in the Database
                  If you want to present the end user with a list of locales to select from, you can call the
                  Session.GetTimeZoneChoiceList method. This method returns a ChoiceList object
                  representing the time zones in the time_zone table in the database. (For more information on
                  ChoiceList objects, see Working with ClarifyCRM Choice and User Choice Lists on page 108.)

                  The following example demonstrates how to use the Session.GetTimeZoneChoiceList
                  method to create an HTML SELECT element with which the end user can select the current time
                  zone:
                  <SELECT NAME="timezone">
                     <OPTION
                     VALUE="ALBST">Alaska Bering Time</OPTION>
                     ...
                     <OPTION
                     VALUE="PST">Pacific Standard Time</OPTION>
                  </SELECT>




282
                                                                 CHAPTER 8   Setting Up Your Application




This example provides the end user with a selection of all time zones in the database. Each option
in the SELECT element displays the description of the time zone. The value submitted by this form
element is the name of the selected time zone. In the page that processes the submitted form, you
can use the value of the time zone element to set the time zone for the current session.
   In Java (JSP):
   <%@ page import="com.clarify.cbo.*" %>
   ...
   <SELECT NAME="timezone">
   <%
   int nItem = 0;
   Choice chTimeZone;

   // Get the current time zone.
   strTimeZone = (String) ClfySession.getItem("Locale.TimeZone");

   // Get the list of time zones.
   ChoiceList chListTimeZone = ClfySession.getTimeZoneChoiceList();

   // Create an OPTION tag for each time zone.
   for (nItem = 1; nItem <= chListTimeZone.getCount(); nItem++) {
      chTimeZone = chListTimeZone.getItem(nItem);
      %>
      <OPTION
      <%
      if (strTimeZone.equals(chTimeZone.getTitle())) {
         %>
         SELECTED
         <%
      }
      %>
      VALUE="<%=chTimeZone.getTitle()%>">
         <%=chTimeZone.getDescription()%></OPTION>
      <%
   }
   %>
   </SELECT>
   In JavaScript (ASP):
   <SELECT NAME="timezone">
   <%
   // Get the list of time zones.
   var chListTimeZone = ClfySession.GetTimeZoneChoiceList();
   // Create an OPTION tag for each time zone.
   for (var nItem = 1; nItem <= chListTimeZone.Count; nItem++) {
      var chTimeZone = chListTimeZone.Item(nItem);
      %>
      <OPTION
      VALUE="<%=chTimeZone.Title%>"><%=chTimeZone.Description%>
      </OPTION>
      <%
   }
   %>
   </SELECT>




                                                                                                    283
Setting the Locale and Time Zone




                  Setting the Time Zone
                  When you use the Session.SetLocale method to set the end user’s time zone in the
                  ClarifyCRM Session object, you need to specify the time zone by name (as one of the names listed
                  in the time_zone table) or by hours relative to GMT:
                  GMT<+|-><num_hrs>[.5]

                  For example:
                  GMT
                  GMT+2.5
                  GMT-3

                  The following JavaScript statement sets the locale and time zone for the current session:
                  ClfySession.SetLocale(strLocaleCode, strTimeZone);

                  The following statement performs the equivalent operation using Java:
                  ClfySession.setLocale(strLocaleCode, strTimeZone);


                  Example of Setting the Locale and Time Zone in JSP
                  This section contains two JSP examples that demonstrate how to prompt the end user for the locale
                  and time zone:
                      The first code example (JSP Code for the Form on page 284) generates an HTML form for
                      selecting the locale and time zone.
                      The second code example (JSP Code for Handling the Form Submission on page 286) gets the
                      results from the previous form and uses the results to set the locale and time zone for the
                      current session.

                  JSP Code for the Form
                  The following example of JSP code is written in Java. The example generates an HTML form that
                  contains two SELECT elements:
                      The element named locale provides a list of all locales in the database.
                      The element named timezone provides a list of all time zones in the database.

                  When you submit this form, the locale code for the selected locale and the name of the selected time
                  zone are submitted.

                  The code for handling the submission of this form is shown in JSP Code for Handling the Form
                  Submission on page 286.
                  <%@ page import="com.clarify.cbo.*,java.util.*" %>
                  ...
                  <%
                  ChoiceList chListTimeZone = null;
                  Choice chTimeZone = null;
                  String strLocaleCodeList = null;
                  String strLocaleCodeItem = null;
                  String strCountryList = null;
                  String strCountryItem = null;
                  String strLanguageList = null;
                  String strLanguageItem = null;
                  int nKey = 0;


284
                                                      CHAPTER 8   Setting Up Your Application




String[]   arrayLocale;
String[]   arrayLanguage;
String[]   arrayCountry;
String[]   arrayTZ;
%>

<HTML>
<HEAD>
<TITLE>Setting the Locale and Timezone</TITLE>
</HEAD>
<BODY>
<FORM METHOD=GET ACTION="SetLocale.jsp">
Locale: <SELECT NAME="locale">
<% // Get the list of locale codes.
strLocaleCodeList = ClfySession.getLocaleNameList(
   CboConstants.cboLocaleShowLocaleCode);

// Get the list of languages.
strLanguageList = ClfySession.getLocaleNameList(
   CboConstants.cboLocaleShowLanguage);

// Get the list of countries.
strCountryList = (String)ClfySession.getLocaleNameList(
   CboConstants.cboLocaleShowCountry);

// Convert the list of locale codes to an array of locale codes.
StringTokenizer st = new StringTokenizer(strLocaleCodeList, ",");
arrayLocale = new String[st.countTokens()];
while (st.hasMoreTokens()) {
   arrayLocale[nKey] = st.nextToken();
   nKey++;
}

// Convert the list of languages to an array of languages.
nKey = 0;
st = new StringTokenizer(strLanguageList, ",");
arrayLanguage = new String[st.countTokens()];
while (st.hasMoreTokens()) {
   arrayLanguage[nKey] = st.nextToken();
   nKey++;
}

// Convert the list of country codes to an array of country codes.
nKey = 0;
st = new StringTokenizer(strCountryList, ",");
arrayCountry = new String[st.countTokens()];
while (st.hasMoreTokens()) {
   arrayCountry[nKey] = st.nextToken();
   nKey++;
}

// Create an OPTION tag for each locale.
for (nKey = 0; nKey < arrayLocale.length; nKey++) {
   strLocaleCodeItem = arrayLocale[nKey];
   strLanguageItem = arrayLanguage[nKey];
   strCountryItem = arrayCountry[nKey];
   strLocaleCodeItem = strLocaleCodeItem.trim();


                                                                                         285
Setting the Locale and Time Zone




                      strLanguageItem = strLanguageItem.trim();
                      strCountryItem = strCountryItem.trim();
                      %>
                      <OPTION

                     VALUE="<%=strLocaleCodeItem%>"><%=strLanguageItem%>
                             (<%=strCountryItem%>)</OPTION>
                     <%
                  } %>
                  </SELECT><BR>
                  Time Zone: <SELECT NAME="timezone">
                  <%
                  // Get the list of time zones.
                  chListTimeZone = ClfySession.getTimeZoneChoiceList();

                  // Create an OPTION tag for each time zone.
                  for (nKey = 1; nKey <= chListTimeZone.getCount(); nKey++) {
                     chTimeZone = chListTimeZone.getItem(nKey);
                     %>
                     <OPTION
                     VALUE="<%=chTimeZone.getTitle()%>">
                     <%=chTimeZone.getTitle()%>:&nbsp;
                     <%=chTimeZone.getDescription()%></OPTION>
                     <%
                  } %>
                  </SELECT><BR>
                  <INPUT TYPE=SUBMIT VALUE="Set Locale and Timezone">
                  </FORM>
                  </BODY>
                  </HTML>

                  JSP Code for Handling the Form Submission
                  The following example of JSP code is written in Java. The example gets the values of the form
                  elements named locale and timezone (submitted through the HTTP GET method) and uses these
                  values to set the locale and time zone for the current session. Then, the JSP code gets the locale and
                  time zone from the ClarifyCRM Session object and generates an HTML page that displays this
                  information.
                  <%@ page import="com.clarify.cbo.*,java.util.*" %>
                  <jsp:useBean id="cboWebSupStr" scope="session"
                     class="com.clarify.common.CboWebSupStr" />
                  ...
                  <HTML>
                  <HEAD>
                  <TITLE>Current Locale and Timezone</TITLE>
                  </HEAD>
                  <BODY>
                  <%
                  String strLocaleCode = "en-us";
                  String strTimeZone = "PST";

                  // Get the form value submitted for the locale code.
                  if (cboWebSupStr.getParameter(request, session, "locale") != null) {
                     strLocaleCode =
                        cboWebSupStr.getParameter(request, session, "locale");
                  }


286
                                                                   CHAPTER 8   Setting Up Your Application




// Get the form value submitted for the time zone.
if (cboWebSupStr.getParameter(request, session, "timezone") != null) {
   strTimeZone =
      cboWebSupStr.getParameter(request, session, "timezone");
}
// Set the locale and time zone for the session.
ClfySession.setLocale(strLocaleCode, strTimeZone);
%>
Current Locale: <%=ClfySession.getItem("Locale.Name")%><BR>
Current Time Zone: <%=ClfySession.getItem("Locale.TimeZone")%><BR>
</BODY>
</HTML>


Example of Setting the Locale and Time Zone in ASP
This section contains two ASP examples that demonstrate how to prompt the end user for the
locale and time zone:
   The first code example (ASP Code for the Form on page 287) generates an HTML form for
   selecting the locale and time zone.
   The second code example (ASP Code for Handling the Form Submission on page 289) gets the
   results from the previous form and uses the results to set the locale and time zone for the
   current session.

ASP Code for the Form
The following example of ASP code is written in JavaScript. The example generates an HTML form
that contains two SELECT elements:
   The element named locale provides a list of all locales in the database.
   The element named timezone provides a list of all time zones in the database.

When you submit this form, the locale code for the selected locale and the name of the selected time
zone are submitted.

The code for handling the submission of this form is shown in ASP Code for Handling the Form
Submission on page 289.
<%@ Language=JavaScript %>
<%Response.Buffer = true%>
... (omitted code for getting the Clarify Application object and
      for creating the Clarify Session object)...
<HTML>
<HEAD>
<TITLE>Setting the Locale and Timezone</TITLE>
</HEAD>
<BODY>
<FORM METHOD=GET ACTION="SetLocale.asp">
Locale: <SELECT NAME="locale">
<%
// Get the list of locale codes.
var strLocaleCodeList =
   ClfySession.GetLocaleNameList(cboLocaleShowLocaleCode |
   cboLocaleShowAllItems);




                                                                                                      287
Setting the Locale and Time Zone




                  // Get the list of languages.
                  var strLanguageList =
                     ClfySession.GetLocaleNameList(cboLocaleShowLanguage |
                     cboLocaleShowAllItems);

                  // Get the list of countries.
                  var strCountryList =
                     ClfySession.GetLocaleNameList(cboLocaleShowCountry |
                     cboLocaleShowAllItems);

                  // Remove any extra spaces between commas.
                  re = /\s*,\s*/g;
                  strLocaleCodeList = strLocaleCodeList.replace(re, ",");
                  strLanguageList = strLanguageList.replace(re, ",");
                  strCountryList = strCountryList.replace(re, ",");

                  // Remove any spaces in the list of locale codes.
                  re = /\s/g;
                  strLocaleCodeList = strLocaleCodeList.replace(re, "");

                  // Remove any leading and trailing spaces.
                  re = /^\s*(\S*)\s*$/g;
                  strLocaleCodeList = strLocaleCodeList.replace(re, "$1");
                  strLanguageList = strLanguageList.replace(re, "$1");
                  strCountryList = strCountryList.replace(re, "$1");

                  // Convert the lists to arrays.
                  var arrayLocale = strLocaleCodeList.split(",");
                  var arrayLanguage = strLanguageList.split(",");
                  var arrayCountry = strCountryList.split(",");

                  // Create an OPTION tag for each locale.
                  for (var nKey in arrayLocale) {
                     strLocaleCodeItem = arrayLocale[nKey];
                     strLanguageItem = arrayLanguage[nKey];
                     strCountryItem = arrayCountry[nKey];

                      // Create an option for this locale code.
                      %>
                      <OPTION
                      VALUE="<%=strLocaleCodeItem%>"><%=strLanguageItem%>
                         (<%=strCountryItem%>)</OPTION>
                      <%
                  }
                  %>
                  </SELECT><BR>
                  Time Zone: <SELECT NAME="timezone">
                  <% // Get the list of time zones.
                  var chListTimeZone = ClfySession.GetTimeZoneChoiceList();
                  // Create an OPTION tag for each time zone.
                  for (var nItem = 1; nItem <= chListTimeZone.Count; nItem++) {
                     var chTimeZone = chListTimeZone.Item(nItem);
                     %>
                     <OPTION
                     VALUE="<%=chTimeZone.Title%>"><%=chTimeZone.Description%></OPTION>
                     <%
                  } %>


288
                                                                  CHAPTER 8   Setting Up Your Application




</SELECT><BR>
<INPUT TYPE=SUBMIT VALUE="Set Locale and Time Zone">
</FORM>
</BODY>
</HTML>

ASP Code for Handling the Form Submission
The following example of ASP code is written in JavaScript. The example gets the values of the
form elements named locale and timezone (submitted through the HTTP GET method) and uses
these values to set the locale and time zone for the current session. Then, the ASP code gets the
locale and time zone from the ClarifyCRM Session object and generates an HTML page that
displays this information.
<%@ Language=JavaScript %>
<%Response.Buffer = true%>
... (omitted code for getting the Clarify Application object and
     for creating the Clarify Session object)...
<%
var strLocaleCode = "en-us";
var strTimeZone = "PST"

// Determine if locale already submitted.
if (Request.QueryString("locale").Count) {
    strLocaleCode = Request.QueryString("locale");
}
// Determine if time zone is already submitted.
if (Request.QueryString("timezone").Count) {
    strTimeZone = Request.QueryString("timezone");
}
// Set the locale for the session.
ClfySession.SetLocale(strLocaleCode, strTimeZone);
%>
<HTML>
<HEAD>
<TITLE>Current Locale and Timezone</TITLE>
</HEAD>
<BODY>
<P>
Current Locale: <%=ClfySession.Item("Locale.Name")%><BR>
Current Time Zone: <%=ClfySession.Item("Locale.TimeZone")%><BR>
</P>
</BODY>
</HTML>




                                                                                                     289
Logging In to the Database




Logging In to the Database
                  To use the business objects to query and update data, you first need to log in to the database. To do
                  this, use the Session.Login or the Session.PredefinedLogin methods. (For a complete
                  description of these methods, see the Core Business Objects Reference Guide.)

                  When you log in, there are a number of options that you can specify:
                      You can use the login name and password of an employee, a submitter, or a web user to log in to
                      the database. For details, see Logging In as Different Types of Users on page 290.
                      You can use external authentication to log in a user. For details, see Logging in Using External
                      Authentication on page 291.
                      You can specify that activities are performed on behalf of another user. For details, see Setting
                      the Proxy on page 291.
                      You can specify the duration of time in minutes for which the given session can stay idle. For
                      details, see Setting the Timeout on page 291.
                      You can log in as a user defined in ClarifyEnv.xml. For details, see Logging in Using a
                      Predefined Login on page 291.
                      By default, you log in to the database specified by the connection information in the
                      clarify.env file. You can change this information to connect to a different database or set up
                      additional database connections with other databases. For details, see Changing Database
                      Connection Information on page 293.


                  Logging In as Different Types of Users
                  The third parameter of the Login method specifies the type of user. You can log in the following
                  types of users:
                      A ClarifyCRM user who is an employee (use the login type cboUserEmployee)
                      A ClarifyCRM user who is a submitter (use the login type cboUserContact)
                      The web user for a ClarifyCRM user who is an employee (use the login type
                      cboWebUserEmployee)
                      The web user for a ClarifyCRM user who is a submitter (use the login type
                      cboWebUserContact)
                      The web user for a ClarifyCRM contact who is not a submitter (use the login type
                      cboWebContactOnly)

                  If you want to use these constants in Java, use the static fields defined in the CboConstants class
                  (for example, CboConstants.cboWebContactOnly).

                  For information on the terms submitter and web user, see the Master Glossary.

                  The following sections of code are examples of using the Session.Login method:
                      The following example of Visual Basic code logs in an employee as a ClarifyCRM user:
                      ClfySession.Login "employeeLogin", "secret"
                      The following example of Visual Basic code logs in a contact (who is a submitter) as a
                      ClarifyCRM user:
                      ClfySession.Login "submitterlogin", "secret", cboLoginUserContact


290
                                                                    CHAPTER 8   Setting Up Your Application




   The following example of JavaScript code logs in an employee as a web user:
   ClfySession.Login("webemployee", "secret", cboLoginWebUserEmployee)
   The following example of JavaScript code logs in a contact (who is a submitter) as a web user:
   ClfySession.Login("websubmitter", "secret", cboLoginWebUserContact)


Setting the Proxy
The fourth argument is the login name of an existing ClarifyCRM user. This user becomes a proxy
for the contact. Any activities performed by the contact are performed as this user.

For example, if a contact creates a case, this proxy user becomes the creator and owner of the case.
If a contact logs a note, this proxy user performs the activity of logging the note. The activity log
entry for the logged note shows the proxy user as the user who performed this activity.

In the System Administration Guide, this user is referred to as the CBO application user.

The following example of Java code logs in a contact (who is not a submitter) as a web user:
   ClfySession.login("webcontact", "secret",
      CboConstants.cboLoginWebContactOnly, "proxyClarifyUser", 1, 30)


Setting the Timeout
The sixth argument in the Login method specifies the time in minutes after which an idle logged
in session should expire. You can set the value of this argument to “-1” if you do not want the
Session object to expire.


Logging in Using a Predefined Login
If you are running a server process that does not get the login and password from a user
interactively, you can use the Session.PreDefinedLogin method to get the login and
password from the clarify.env file. This method uses the predef_user_name and
predef_user_password settings in this file to log in a user. The predef_user_name setting
must be the login name of a valid ClarifyCRM user.


Logging in Using External Authentication
CBO lets you authenticate the user name and password using an external source in several ways.

If you want to authenticate a ClarifyCRM user in an external system (versus using CBO), you can
bypass the authentication done by the Login method.

You call the Login method after setting its password argument to an empty string and its user
argument to a valid ClarifyCRM user. You can then use any programmatic method you want to
authenticate the user.

Besides writing totally custom code, you also have a couple of standard ways of working with an
external authentication source when you bypass authentication.




                                                                                                       291
Logging In to the Database




                  Call this signature of the login method to log in using a Subject object you get from an external
                  authentication source:
                  public void login(javax.security.auth.Subject subject, int loginType, String
                  strAssocDefName, int connection, int expirationTimeInMinutes)

                  The Subject object contains the user name and password and credentials that represent
                  security-related attributes such as cryptographic keys or public key certificates. You can access the
                  Subject object by calling addCredential, findCredential, getSubject, and
                  isCredentialExist in the Session object.

                  One of the credentials in this Subject object must be a
                  com.clarify.sam.AppConnCredentialMap type which is a hash map of keys. Each key in
                  this map either refers to a resource-specific user or resource-specific password. One of these keys
                  also identifies the ClarifyCRM user. The exact key name that identifies the ClarifyCRM user is
                  specified in ClarifyEnv.xml by the ClarifyUserNameKey statement. The ClarifyCRM user
                  name retrieved from the credential is used to log into the Session object without password
                  verification.

                  Call this signature of the login method to authenticate a user using an external source that
                  implements the LoginModule interface:
                  public void login(String user, String clearTextPassword, int loginType, String
                  strAssocDefName, int connection, int expirationTimeInMinutes, boolean bExternAuth,
                  Map additionalCredentials)

                  You must set the bExternAuth parameter to True. You can pass credentials to the authentication
                  source in additionalCredentials. If authentication is successful, this form of the login
                  method internally returns a Subject object that represents the source of the request which you can
                  access with the methods listed above.

                  The LoginModule interface is part of JAAS (Java Authentication and Authorization Service)
                  which is a framework for implementing pluggable authentication modules. You can write an
                  LoginModule class yourself or use one from a third party.

                  You must also configure the LoginModule class in ClarifyEnv.xml. For example:
                  <SingleSignOn class="test.sam.TestLoginModule"
                        clarify_login_name_key="ClarifyUserName"
                        clarify_password_key="ClarifyPassword">
                     <LoginModule value1="value1" value2="val2"/>
                  </SingleSignOn>

                  For more about external authentication, see the System Administration Guide.




292
                                                                       CHAPTER 8   Setting Up Your Application




Changing Database Connection Information
By default, when you use the business objects to connect to the database, the CBO Data Service
uses the database connection information (the login, password, server name, and database name)
specified in the clarify.env file. (For details on the location of this file and its settings, see the
System Administration Guide.)

The CBO Data Service also supports the ability to establish up to seven different database
connections. These connections are called channels. The default channel (known as channel 0) uses
connection information specified in the clarify.env file.

You can programmatically change the database connection information for the default channel or
specify connection information for additional channels by using the
Application.SetAlternateDBInfo method:
   If you want to change the connection information for the default channel (overriding the
   settings in the clarify.env file), use the Application.SetAlternateDBInfo method,
   and pass 0 as the first argument (which specifies that you want to modify the connection
   information for the default channel, channel 0):
   Application.SetAlternateDBInfo(0, DatabaseServer, DatabaseName,
      DatabaseLogin, DatabasePassword);
   To override the clarify.env file settings, call this method before creating any Session
   objects.
   If you want to establish a connection to a different database, use the
   Application.SetAlternateDBInfo method and specify the channel ID as the first
   argument:
   Application.SetAlternateDBInfo(ChannelID, DatabaseServer,
      DatabaseName, DatabaseLogin, DatabasePassword);
   Then, you can use the Session.Login method to log in to that channel, specifying the channel
   ID as the last argument:
   Session.Login(Login, Password, LoginType, ProxyLogin, ChannelID);

In the Application.SetAlternateDBInfo method, the DatabaseLogin and
DatabasePassword arguments represent the login and password of a database user. The CBO
Data Service uses this database user to establish a database connection.

If you are developing a server application that multiple users are logging in to, you should use the
sa user login and password. If you are developing a workstation application that a single user is
logging in to, you can use the database login for any ClarifyCRM user.

Note: This database user does not become the owner of any newly created workflow items or the user recorded
in activity log entries. Instead, the ClarifyCRM user that you specify in the Session.Login method
becomes the owner of any newly created workflow items and the user recorded in activity log entries. For
more information, see the System Administration Guide.




                                                                                                          293
Logging In to the Database




                  For example, the following sections of code demonstrate how to change the connection information
                  for the default channel to use the database login dbuser, the password dbpassword, the database
                  server clfyserver, and the database name clarifydb:
                      In Java:
                      import com.clarify.cbo.*;
                      ...
                      Application clfyApp = null;
                      Session clfySession = null;
                      String strNewDBServer = "clfyserver";
                      String strNewDBName = "clarifydb";
                      String strNewLogin = "dbuser";
                      String strNewPassword = "dbpassword";
                      String strClarifyUser = "clfyuser";
                      String strClarifyPassword = "clfypassword";
                      ...
                      // Create the Application object.
                      clfyApp = new Application();
                      // Change the connection information for the default channel 0.
                      clfyApp.setAlternateDBInfo(0, strNewDBServer, strNewDBName,
                         strNewLogin, strNewPassword);
                      // Create a new Session object.
                      clfySession = clfyApp.createSession();
                      // Log in to that session using the default channel.
                      clfySession.login(strClarifyUser, strClarifyPassword);
                      ...
                      In JavaScript:
                      var strNewDBServer = "clfyserver";
                      var strNewDBName = "clarifydb";
                      var strNewLogin = "dbuser";
                      var strNewPassword = "dbpassword";
                      var strClarifyUser = "clfyuser";
                      var strClarifyPassword = "clfypassword";
                      ...
                      // Create the Application object.
                      var clfyApp = new ActiveXObject("Clarify.CBO.App.1");
                      // Change the connection information for the default channel 0.
                      clfyApp.SetAlternateDBInfo(0, strNewDBServer, strNewDBName,
                         strNewLogin, strNewPassword);
                      // Create a new Session object.
                      var clfySession = clfyApp.CreateSession();
                      // Log in to that session using the default channel.
                      clfySession.Login(strClarifyUser, strClarifyPassword);
                      ...




294
                                                                 CHAPTER 8   Setting Up Your Application




The following sections of code demonstrate how to set up channel 1 to use the database login
altdbuser, the password altdbpassword, the database server altclfyserver, and the
database name altclarifydb:
   In Java:
   import com.clarify.cbo.*;
   ...
   Application clfyApp = null;
   Session clfyAltSession = null;
   String strAltDBServer = "altclfyserver";
   String strAltDBName = "altclarifydb";
   String strAltLogin = "altdbuser";
   String strAltPassword = "altdbpassword";
   String strClarifyUser = "clfyuser";
   String strClarifyPassword = "clfypassword";
   ...
   // Create the Application object.
   clfyApp = new com.clarify.cbo.Application();
   // Change the connection information for channel 1.
   clfyApp.setAlternateDBInfo(nChannelId, strAltDBServer,
      strAltDBName, strAltLogin, strAltPassword);
   // Create a new Session object.
   clfySessionAlt = clfyApp.createSession();
   // Log in to that session using channel 1.
   clfySessionAlt.login(strClarifyUser, strClarifyPassword,
      CboConstants.cboLoginUserEmployee, "", nChannelId);
   ...
   In JavaScript:
   var strAltDBServer = "altclfyserver";
   var strAltDBName = "altclarifydb";
   var strAltLogin = "altdbuser";
   var strAltPassword = "altdbpassword";
   var strClarifyUser = "clfyuser";
   var strClarifyPassword = "clfypassword";
   var cboLoginUserEmployee = 5;
   var nChannelId = 1;
   ...
   // Create the Application object.
   clfyApp = new ActiveXObject("Clarify.CBO.App.1");
   // Change the connection information for channel 1.
   clfyApp.SetAlternateDBInfo(nChannelId, strAltDBServer,
      strAltDBName, strAltLogin, strAltPassword);
   // Create a new Session object.
   clfySessionAlt = clfyApp.CreateSession();
   // Log in to that session using channel 1.
   clfySessionAlt.Login(strClarifyUser, strClarifyPassword,
      cboLoginUserEmployee, "", nChannelId);
   ...




                                                                                                    295
Getting the Context for the Business Objects




Getting the Context for the Business Objects
                   The BoContext object (in Java) and the FormContext object (in ActiveX) represent a container
                   for business objects. The BoContext/FormContext object provides the ability to perform bulk
                   queries and global transactions. (For more information, see Chapter 9, Using Global Transactions and
                   Bulk Queries.)

                   In addition, the BoContext/FormContext object provides access to the logged-in session and
                   provides the ability to create new business objects.

                   When you drop a business object on a Visual Basic form, a FormContext object is created
                   automatically and is associated with that business object. Any subsequent business objects that you
                   drop on the form are automatically associated with the same FormContext object.

                   If you are creating business objects programmatically (instead of dropping them on a form) in
                   Visual Basic, you need to manually create the FormContext object for the current form.

                   To create a new BoContext/FormContext object, call the Session createBoContext method
                   (in Java) or the CreateFormContext method (in ActiveX). The following examples creates a new
                   BoContext object and FormContext object from the Session object ClfySession:
                       In Java:
                       BoContext clfyBoContext = clfySession.createBoContext();
                       In Visual Basic:
                       Set ClfyForm = ClfySession.CreateFormContext

                   Note: In Visual Basic, if you dropped business objects on the form, you do not need to create a
                   FormContext object. A FormContext object is created automatically by the dropped controls. See
                   Working with Dropped Controls in Visual Basic on page 303.




Creating Business Objects
                   After you create the BoContext/FormContext object, you can use the CreateBO method or the
                   CreateGenericBO to create new business objects programmatically.

                   Note: In Visual Basic, you can also create business objects by dropping them on a form. (See Working with
                   Dropped Controls in Visual Basic on page 303.)


                   When you call the CreateGenericBO method to instantiate a Generic business object, you need
                   to specify the table in the database that the business object represents as an argument:
                       Generic boCase = clfyBoContext.createGenericBO("case");

                   Note: Once you create a Generic business object, you cannot change its DBObjectName property.


                   For details of the CreateGenericBO method, see the documentation in the Core Business Objects
                   Reference Guide.




296
                                                                           CHAPTER 8   Setting Up Your Application




        When you call the CreateBO method, you need to specify one of the following values as an
        argument:
           In Java, specify the class name of the business object. For example:
           import com.clarify.cbo.*;
           ...
           Case boCase = (Case)clfyBoContext.createBO("com.clarify.cbo.Case");
           In ActiveX, specify the programmatic identifier for the business object. For example, in Visual
           Basic:
           Dim BoCase
           Set BoCase = ClfyForm.CreateBO("Clarify.CBO.Case")

        For a complete listing of class names and programmatic identifiers, see the documentation on the
        CreateBO method in the Core Business Objects Reference Guide.




Getting Configuration and Profile Information
        When you log in to the database, the Session object reads and caches the configuration items
        from the config_itm table and user context information from the user, employee, and
        contact tables (depending on who is logged in).

        You can get this data (as well as information on the current locale and currency attributes) from the
        Session.Item property. For a complete listing of the data available through this property, see the
        Core Business Objects Reference Guide.

        The following example gets the user’s first and last names and the str_value field value for the
        config_itm row with name = "Web Attachments Dir":
           In Java (JSP):
           <%@ page import="com.clarify.cbo.*" %>
           ...
           Session ClfySession;
           ...
           <!-- Print the first and last name of the contact.-->
           <p>Welcome, <%=ClfySession.getItem("first_name")%>&nbsp;
           <%=ClfySession.getItem("last_name")%>!</p>
           <!-- Print the str_value of the config item. -->
           <p>Value of Web Attachments Dir:
           <%=ClfySession.getItem(
              "config_itm.Web Attachments Dir.str_value")%></p>
           In JavaScript (ASP):
           <%
           var ClfySession;
           %>
           ...
           <!-- Print the first and last name of the contact.-->
           <p>Welcome, <%=ClfySession.Item("first_name")%>&nbsp;
              <%=ClfySession.Item("last_name")%>!</p>
           <!-- Print the str_value of the config item. -->
           <p>Value of Web Attachments Dir:
              <%=ClfySession.Item(
              "config_itm.Web Attachments Dir.str_value")%></p>


                                                                                                              297
Getting Diagnostic Information on CBO




                  The following is an example of the HTML generated by this code:
                  <p>Welcome, Jane&nbsp;Doe!</p>
                  <p>Value of Web Attachments Dir: d:\temp2</p>




Getting Diagnostic Information on CBO
                  In this release, a new MBean called the CBODiagnostics, has been introduced to provide diagnostic
                  information on CBO. An MBean, or managed bean, is a Java object that represents a JMX
                  manageable resource. MBeans can be registered with any JMX Agent. The MBean server within a
                  JMX Agent serves as a registry for MBeans and provides the services that allow MBeans to be
                  manipulated.

                  The CBODiagnostics MBean is shipped as part of ClfyCore.jar with the ClarifyCRM web
                  applications. To access the diagnostic information provided by the MBean, the ClarifyCRM web
                  applications must first be deployed on either WebLogic Server or the WebSphere Application
                  Server. The application server acts as the JMX agent. When the web applications are deployed, the
                  MBean are also registered on the server. Once registered, use any JMX Client tool to connect to the
                  application server and access the diagnostic MBean to track various data.

                  The following are some of the statistics provided by CBODiagnostics:
                      the number of BOs object instances
                      the number of session object instances
                      the number of flexible attributes




Ending the CBO Session
                  To end the current CBO session, use one of the following methods:
                      In ActiveX, use the Session.Logout method.
                      In Java, use the Session.release method if your application did not get the session ID from
                      the Session object.
                      If you did get the session ID, use the Session.logout method to end the session. (For more
                      information on getting the session ID, see Tracking a Session with the Session ID on page 272.)

                  After you end the session, you cannot use that same Session object to log in again. Instead, you
                  should create a new Session object.




Working in Different Programming Languages
                  This section provides some guidelines for working with the business objects in different
                  programming languages.
                      Working with Business Objects in Java on page 299
                      Working with Business Objects in JavaScript on page 303


298
                                                                  CHAPTER 8   Setting Up Your Application




   Working with Business Objects in Visual Basic on page 303


Working with Business Objects in Java
When developing a Java application or JSP pages with the business objects, follow the guidelines
explained in these sections:
   Setting Up Your Application on page 299
   Specifying Constants and Flags on page 299
   Working with Currency and Decimal Values on page 300
   Logging Trace Messages on page 300
   Releasing Objects from Memory on page 300
   Checking the Status of the Application on page 302
   Ending the Current Session on page 302
   Exiting the Application on page 302

For information about using CBOs and XVOs in Enterprise Java Beans (EJBs) and EJB clients, see
the Integration Gateway Implementation Guide.

Setting Up Your Application
The business objects are part of the com.clarify.cbo package. For convenience, you may want
to import this package.

The classes in this package are contained in the ClfyCore.jar file. The CBO infrastructure also
includes classes in the ClfyXvo.jar file. (The ClfyXvo.jar file contains the generated XVO
classes. For details on XVOs, see Chapter 3, Working with XVOs. For information on generating
these classes, see Chapter 3, Working with XVOs.)

When you are compiling applications, your classpath must include ClfyCore.jar, and (if you are
using XVO classes) ClfyXvo.jar. To run a CBO application, your classpath must include
ClfyCore.jar and (if you are using XVOs) ClfyXvo.jar file.

See the System Administration Guide for additional instructions on setting up your environment to
run CBO applications.

Specifying Constants and Flags
In Java, constants and flags (such as cboSubmitDisabled, cboFindNext, and
cboTypeInteger) are defined as static fields in the com.clarify.cbo.CboConstants class.

To specify these constants or flags, use the fields in the CboConstants class. For example:
import com.clarify.cbo.*;
...
String strDateTime = clfySession.toSQLString(calInput,
   CboConstants.cboSQLQuote, CboConstants.cboTypeDateTime));




                                                                                                     299
Working in Different Programming Languages




                 Working with Currency and Decimal Values
                 If you are working with currency and decimal values, the business objects return values of these
                 types as strings (for example, field values or values returned by the
                 Calculator.GetCalcResult method)

                 Logging Trace Messages
                 You can configure the ClarifyCRM Business Objects and the CBO Data Service to generate a trace
                 log file through settings in the clarify.env file. For information about these settings and the
                 generated trace log, see System Administration Guide.

                 If you want to log your own messages to this file, you can create your own trace keys and use the
                 Session.trace method with these keys.

                 When naming your trace keys, you should ensure that other applications are not using the same
                 names by ending your trace key names with “_x”. (If you are writing an application for your
                 customers to customize, you should end your trace key names with your own identifier, in order to
                 reserve the “_x” suffix for your customers.)

                 If the TraceKeys setting in the clarify.env file includes the name of your trace key, CBO logs
                 your specified message to the trace log file.

                 For example, the following section of Java code generates a message in the trace log file if the
                 TraceKeys setting in the clarify.env file includes the VerboseMessage_x key:
                 String strTraceKey = "VerboseMessage_x";
                 String strMessage = "Initializing settings...\n";
                 ClfySession.trace(strTraceKey, strMessage);

                 Releasing Objects from Memory
                 Most of the classes have a release method (for example, BO.release, BoContext.release,
                 Application.release) that releases any resources used by a Java object. When you no longer
                 need to use an object, you should call the release method of that object.

                 The business objects are C++ objects with a Java layer. When you release the Java object, the C++
                 object is also released from memory. Because the amount of memory used by the C++ objects is
                 significantly greater than the amount of memory used by the Java objects, you should manually
                 invoke the release method for any unused objects, rather than waiting for the Java garbage
                 collector to run. Although the Java garbage collector may run eventually to release these Java and
                 C++ objects, releasing them explicitly may provide better performance and scalability.

                 The details of the release methods for the different classes is explained below:
                     The BoContext.release method releases any resources held in memory by the associated
                     business objects and by the BoContext object itself.
                     You should call the BoContext.release method after any updates have been committed to
                     the database and you no longer need the data in the business objects for that BoContext object.

                     Note: In a component that is enlistable in a global transaction, do not release the BoContext object for
                     the transaction. The application code that begins and commits the global transaction is responsible for
                     releasing that BoContext object.




300
                                                                   CHAPTER 8   Setting Up Your Application




   The BO.release method releases the business object and unregisters the business object from
   its associated BoContext object.
   As a general guideline, you should call the BO.release method for a business object after you
   are done using the object and before the object goes out of scope.
   If you are writing a component that is enlistable in a global transaction, do not release any
   business objects that participate in the transaction. For more information on global transactions,
   see Using Global Transactions on page 314.
   The Session.release method releases any resources held in memory by the associated
   BoContext objects, by the business objects, and by the Session object itself.
   This method also ends the current user’s CBO session if your application did not get the session
   ID (see Tracking a Session with the Session ID on page 272 for more information).
   When you want to end a user’s session, call one of the following methods:
   – If you did not get the session ID from the CBO Session object, call the Session.release
     method to end the session.
   – If you did get the session ID, call the Session.logout method to end the session. After
     calling the Session.logout method, you need to explicitly call the Session.release
     method to free objects and other resources.
   The Application.release method releases the Application object.
   Unlike the Session.release and BoContext.release methods, the
   Application.release method does not release all related objects. You need to call
   Session.release or BoContext.release before calling Application.release.
   Before you release the Application object, you should call the Application.pause
   method to allow existing sessions to end and then call the Application.shutdown method.
   For details, see Exiting the Application on page 302.

If you are creating and using the business objects within a try/catch loop, you can call the
release method in the finally clause. For example:
import com.clarify.cbo.*;
...
Application clfyApp = null;
Session clfySession = null;
BoContext clfyBoContext = null;
Generic boGeneric = null;
...
try {
   // Create the Clarify Application, Session, and BoContext objects,
   // and create the business object boGeneric.
   ...
} catch(CboError e)
   // Handle any CBO errors.
   ...
} catch(Exception e) {
   // Handle any exceptions.
   ...
} finally {
   if (clfySession != null) {
      // End the current session and release
      // all associated objects.
      clfySession.release();


                                                                                                      301
Working in Different Programming Languages




                    }
                 }
                 ...

                 Checking the Status of the Application
                 In a CBO application, before your process exists, you call the CBO Application.shutdown
                 method to cleanly shut down CBO.

                 If your application has asynchronous threads that use the CBOs, these threads will get an exception
                 when using CBO if the CBO application is already shut down.

                 To avoid this exception, use the Application.GetStatus method to get the status of the CBO
                 application before making a CBO call.

                 This method returns one of the following status codes:
                     CboConstants.cboApplicationStatusRunning (the CBO application is running)
                     CboConstants.cboApplicationStatusPaused((the CBO application is paused)
                     boConstants.cboApplicationStatusShutdown(the CBO application is shutdown)

                 If you find that the application is in a paused or shutdown state, you can clean up your process in
                 anticipation of the exit.

                 Ending the Current Session
                 When you are done with the session, call the Session.release method to end the current
                 session if the application did not get the session ID from the Session object. If the application did
                 get the session ID, call the Session.logout method instead. (For details, see Tracking a Session
                 with the Session ID on page 272.)

                 Exiting the Application
                 If you need to shut down your CBO Java application or the JSP server that serves your CBO web
                 application, you must follow these steps to shut down cleanly:
                  1. Use the Application.pause method to prevent any new sessions from being created.
                  2. Wait for any existing sessions to complete.
                  3. Use the Application.shutdown method to cleanly shut down CBO (for example, this ends
                     any existing sessions).
                  4. Use the Application.release method to release the Application object.
                  5. Shut down your JSP server or exit from your Java application.

                 With this sequence of steps, CBO can end cleanly before your application unloads the CBO libraries
                 from memory.

                 For example:
                 import com.clarify.cbo.*;
                 ...
                 Application clfyApp;
                 ...
                 try {
                    ...



302
                                                                    CHAPTER 8   Setting Up Your Application




} catch (Exception e) {
   ...
} finally {
   if (clfyApp != null) {
      // Prevent new sessions from being created.
      clfyApp.pause();
      // Wait for existing sessions to end.
      ...
      // Shut down CBO.
      clfyApp.shutdown();
      // Release the resources used by the Application object.
      clfyApp.release();
   }
}


Working with Business Objects in JavaScript
The business objects treat currency and decimals as Variant types that hold currency and decimal
data. JavaScript, however, does not have specific data types for currency and decimals. All numeric
Variant types correspond to the JavaScript Number type, which is a Double.

This means that in JavaScript code, when you get currency or decimal data from a field in a
business object, you can lose precision in the data.

To avoid this potential loss in precision, you can configure the business objects to return currency
and decimal data as string values. To do this, set the Session.JScript property to True.

You should change this setting only if you are using JavaScript. VBScript does not have this
problem. You do not lose precision when working with currency and decimal data in VBScript. If
you set this property to True in VBScript code, your application will experience unnecessary
overhead because it is converting currency and decimal data to string values.


Working with Business Objects in Visual Basic
The following sections cover steps for using the business objects within Visual Basic:
   Working with Dropped Controls in Visual Basic on page 303
   Programmatically Creating Business Objects on page 304
   Writing CBO Applications for Microsoft Visual Basic .NET on page 305

Working with Dropped Controls in Visual Basic
In Visual Basic, you can drop CBOs on a form. You can then set up properties during design time
(you can use the Properties window, or you can right-click the control and choose Properties to
display the properties sheet for the control).

To add the business objects to your toolbox, you need to add the business objects as components to
your Visual Basic Project. From the list of available controls, select the control named ClarifyCRM
Common Business Objects. If this control does not appear in the list of available controls, browse to
the CBO bin directory and select the file named AXCommon.ocx.




                                                                                                       303
Working in Different Programming Languages




                 Note: If you already added the business objects as a reference, you cannot add them as a component. Visual
                 Basic reports a name conflict if you attempt to add a control as a component and as a reference.


                 When you are working with a dropped control, you need to use a different procedure for getting
                 the Session and FormContext objects.

                 When a CBO is dropped on a form, the business object finds the FormContext object for the form.
                 If no FormContext object exists, the CBO creates one automatically. When a FormContext object
                 is created, the object automatically finds the global Session object for the process.

                 In this situation, you do not need to create an Application, Session, or FormContext object.
                 Use the Session and FormContext properties of the business object to get this global Session
                 object and this FormContext object.

                 Note: If you create your own Session and FormContext objects and call the FormContext.Start
                 method, the business objects that you dropped on the form are not queried. The newly created Session and
                 FormContext objects represent a separate connection to the database.


                 In the following example of Visual Basic code, the Case business object has been dropped on the
                 form. The name of the control is Case1. The dropped control is used to get the Session object, log
                 in to the database, get the FormContext object, and create other business objects.
                 Private Sub Form_Load()
                    Dim BoGeneric As Object
                    ‘ Use the Session property to get the global Session object and log in.
                    Case1.Session.Login "username", "password"
                    ‘ Use the FormContext property to get the FormContext object, and create another
                    ‘ business object to query for contacts.
                    Set BoGeneric = Case1.FormContext.CreateGenericBO("contact")
                    BoGeneric.DataFields = "*"
                    ‘ Query all objects on the form.
                    Case1.FormContext.Start
                 ...
                 End Sub

                 In this example, Case1.FormContext and BoGeneric.FormContext specify the same
                 FormContext object. Similarly, Case1.Session and BoGeneric.Session specify the same
                 Session object (the global session).

                 Programmatically Creating Business Objects
                 If you want to programmatically create business objects (as explained in Creating Business Objects
                 on page 296) and use the IntelliSense features in Visual Basic, you need to add the business objects
                 as a reference to your Visual Basic Project. From the list of available references, select the control
                 named ClarifyCRM Common Business Objects. If this control does not appear in the list of
                 available references, browse to the directory where you installed the CBOs, go to the bin directory,
                 and select the file named AXCommon.ocx.

                 Note: If you already added the business objects as a component, you will not be able to add them as a
                 reference. Visual Basic reports a name conflict if you attempt to add a control as a component and as a
                 reference.




304
                                                                        CHAPTER 8   Setting Up Your Application




When declaring your variables, specify the type of business object or utility object that you are
declaring. Make sure to qualify the type by specifying that it is part of the
ClarifyCommonBusObj type library.

For example, the following lines of Visual Basic code declare some business objects and utility
objects:
Dim   ClfyApp
Dim   ClfySession As ClarifyCommonBusObj.Session
Dim   ClfyForm As ClarifyCommonBusObj.FormContext
Dim   BoCase As ClarifyCommonBusObj.Case
Dim   BoGeneric As ClarifyCommonBusObj.Generic
...

Note that there is no type available for the ClarifyCRM Application object in the type library.

Writing CBO Applications for Microsoft Visual Basic .NET
In Microsoft Visual Basic .NET, when you pass a property as an argument to an ActiveX method
(such as a business object method), Visual Basic .NET passes the argument by reference. (Passing an
argument by reference means that the method may modify the argument.) When the method
returns, Visual Basic .NET attempts to call the property setter method to update the value of the
property in the Visual Basic context.

Many of the business object properties are read-only and have no setter methods. If your
application code passes a read-only property to a business object method, an error occurs because
Visual Basic .NET attempts to set that read-only property when the method returns.

Note: Visual Basic 6 does not attempt to call the property setter method upon the return of the method. This
is a change in behavior between Visual Basic 6 and Visual Basic .NET. Code that worked without errors in
Visual Basic 6 may fail in Visual Basic .NET because of this change in behavior.


In reality, Visual Basic .NET does not need to call the property setter method. When a business
object property is passed to a method, only a reference to that business object is passed in, and the
method does not modify that reference. (While a method may modify the underlying object in
some cases, the reference to that object remains unchanged.)

The problem occurs whenever you pass a read-only property to any business object method. You
will typically encounter this problem with the following business object methods:
   Add
   AddById (when passing in the Id property of a contained business object)
   Replace
   ReplaceById
   RelateById
   UnrelateById

To work around this problem, rewrite your code to pass in an expression for the business object
property, instead of passing in a business object property itself.




                                                                                                           305
Working in Different Programming Languages




                 For example, consider the following section of code that passes the siteBo.PrimaryAddress
                 property to the Add method of the caseBo.Address object:
                     Dim caseBo As ClarifyCommonBusObj
                     Dim siteBo As ClarifyCommonBusObj
                     ...
                     siteBo.Query
                     caseBo.Address.Add( siteBo.PrimaryAddress )

                 In this example, when the Add method returns, Visual Basic .NET attempts to set the
                 siteBo.PrimaryAddress property to update the value of this property. However, because the
                 PrimaryAddress property is read-only, an error occurs.

                 To work around the problem in this example, change the last statement to:
                     caseBo.Address.Add( ( siteBo.PrimaryAddress ) )

                 The extra pair of parentheses indicates that the expression ( siteBo.PrimaryAddress )
                 should be evaluated and passed to the Add method. As a result, when the method returns, Visual
                 Basic no longer attempts to set the siteBo.PrimaryAddress property.

                 Note: Visual Basic .NET does not need to set the PrimaryAddress property when the method returns.
                 The PrimaryAddress property is a reference to a business object, and the Add method does not modify
                 this reference.


                 As another example, the following statement also causes the same problem:
                     caseBo.Address.AddById( siteBo.PrimaryAddress.Id )

                 To avoid the error caused by this statement, pass in an expression for the Id property, instead of
                 passing in the Id property itself:
                     caseBo.Address.AddById( (siteBo.PrimaryAddress.Id) )




306
                                                                            CHAPTER 8   Setting Up Your Application




Writing Web Applications with ClarifyCRM Business Objects
        The following sections explain how to develop web applications using CBO:
           Developing Java Web Applications on page 307
           Developing Web Applications with ASP on page 312


        Customizing ClarifyCRM web applications
        See the User Interface Framework Customization Guide and for information on how to customize
        ClarifyCRM web applications.


        Developing Java Web Applications
        The following sections cover guidelines for developing CBO web applications in Java:
           Avoiding Use of JSP Declarations on page 307
           Creating the Application Object in a Java Web Application on page 307
           Logging In and Tracking the User’s Session on page 308
           Tracking the CBO Session Across Different JSP Servers on page 309
           Setting the Locale of the Current Thread on page 310
           Creating the BoContext Object for the Web Application on page 310
           Handling Multiple Threads Per HTTP Session on page 310
           Releasing Objects After Processing an HTTP Request on page 310
           Releasing Objects After HTTP Session Timeouts on page 311

        Avoiding Use of JSP Declarations
        JSP declarations (specified by the syntax <%! declaration %>) declare variables that are global
        to the page. In the JSP page implementation class (the class that implements the JSP page), a
        declaration generates a class variable (as opposed to an instance variable). All objects of the class
        share the same instance of a class variable.

        CBO objects (including business objects, the CBO Session object, and the CBO FormContext
        object) do not support this type of use. For CBO objects, do not declare variables with the <%!
        declaration %> syntax.

        Creating the Application Object in a Java Web Application
        In a web application, you should have only one instance of the CBO Application object. To
        ensure that only one instance is created and to provide access to that instance, you can put a
        reference to the object in the ServletContext object for the web application (in JSP, this
        corresponds to the implicit application object).

        After you create the Application object, you need to call the Application.initialize
        method to initialize the Application object.




                                                                                                               307
Writing Web Applications with ClarifyCRM Business Objects




                  Logging In and Tracking the User’s Session
                  To log in the user, create a new Session object for the user from the Application object and
                  invoke the Session.login method (as explained in Logging In to the Database on page 290).

                  Before or after logging the user in to the database, call the Session.setLocale method to set the
                  user’s locale and time zone in the Session object (see Setting the Locale and Time Zone on page 272.)

                  Once the user logs in, you need to keep track of the logged-in session between HTTP requests. One
                  possible way to do this is to keep a reference to the Session object as an attribute of the
                  HttpSession object (which is persistent across HTTP requests).

                  When processing subsequent HTTP requests, you can get the CBO Session object from the
                  HttpSession object

                  In order for this configuration to work, you also need to do the following:
                      If you are using multiple JSP servers, ensure that all requests from a given browser are handled
                      by the same JSP server.
                      Set up a CBO HttpSessionListener object to handle HTTP session timeouts. (For details,
                      see Handling Timeouts in the HTTP Session on page 308.)

                  Note: The CBO Session object is serializable in order to support the state replication feature of the
                  HttpSession object. In a cluster of web servers, if the web server to which a client request is routed fails, the
                  request gets redirected to a secondary node. A serializable Session object makes it possible for session
                  information to be saved through the redirection, thus making the failover smooth for the client.

                  The CBO Session object is also thread-safe. If two different threads (such as frames from the same frameset)
                  access the session object, the access is serialized.


                  Handling Timeouts in the HTTP Session
                  An HTTP session can end if the timeout period between HTTP requests is exceeded. (Typically, you
                  can configure the timeout period in the JSP server.) When this occurs, you need to log out the
                  existing CBO session. In order to handle this type of situation, you should do the following after
                  processing a login request:
                  1. Create a CBO HttpSessionListener object.
                      The com.clarify.cbo.HttpSessionListener class implements the
                      HttpSessionBindingListener interface. This interface has methods for notifying the
                      HttpSessionListener object when it is bound or unbound from the HttpSession object.
                  2. Use the setSession method of this object to set the CBO Session object in the
                     HttpSessionListener object.
                      When the HTTP session times out, the HttpSessionListener object uses this CBO Session
                      object to log out of the CBO session.
                  3. Set the HttpSessionListener object as an attribute of the HttpSession object.
                      This binds the HttpSessionListener object to the HttpSession object. When the HTTP
                      session times out, the HttpSession object unbinds the HttpSessionListener object.

                  When the HTTP session times out, the HttpSession object invokes the valueUnbound method
                  of the HttpSessionListener object. The valueUnbound method invokes the logout method
                  of the CBO Session object to end the CBO session.


308
                                                                      CHAPTER 8   Setting Up Your Application




Tracking the CBO Session Across Different JSP Servers
If you do not want to use the HttpSession object, instead of keeping the CBO Session object
persistent between requests, you can release the CBO Session object after processing a request
and use the CBO session ID to access the user’s CBO session in subsequent requests. (The user’s
CBO session does not end if you release the CBO Session object from memory. The session is
serialized in the session ID.)

The CBO session ID is a string that represents the state of a specific user’s session. To identify the
browser sending the request, have the browser include the CBO session ID in the request as a
cookie, as part of the requested URL, or as an input field in an HTML form. In your server-side
code, you can get the session ID from the request and use the ID to get the user’s session.

The session tag is the part of the session ID that uniquely identifies a session. To get the session tag,
use the Session.SessionTag property.

In order to create and deploy this type of web application, you must do the following:
   Add code to your web application to put the CBO session ID into a cookie after the user logs in.
   (If you do not want to use cookies, you can append the session ID to every URL and include
   hidden input fields with the session ID in any HTML forms.)
   You also need to add code that retrieves the CBO session ID from each incoming request, creates
   a new CBO Session object, and uses the session ID to reestablish the user’s session. (For
   details, see Getting and Using the CBO Session ID on page 309.)
   At the end of processing each request, release the CBO Session object. (For details, see
   Releasing the CBO Session Object on page 309.)

Getting and Using the CBO Session ID
After processing a user login request, use the Session.SessionId property to get the session ID
for the current session. Then, store the session ID, using a method such as this:
   In a Java Cookie object.
   In a HTML hidden form field (form variable).
   Appended to the URL.

Note: The session ID can contain spaces. If you are appending the ID to a URL, you should URL-encode it
before appending it to the URL.


To use the session ID to reestablish an existing session, call
Application.CreateSessionFromId, specifying the saved session ID as the parameter

Releasing the CBO Session Object
After processing each request, release the CBO Session object that you created. For example:
ClfySession.release();

In this type of web application, since you got the session ID from the CBO Session object, the
Session.release method does not end the current session.

If you need to end the current session, call the Session.logout method.




                                                                                                         309
Writing Web Applications with ClarifyCRM Business Objects




                  Setting the Locale of the Current Thread
                  As explained in Setting the Locale and Time Zone on page 272, you must set the user’s locale and time
                  zone in the Session object to allow CBO to account for differences between the locale and time
                  zone on the user’s machine and the JSP server machine.

                  In addition to setting the locale in the Session object, you must also invoke the
                  Session.establishOnCurrentThread method at the start of the request processing. For
                  example:
                  ClfySession.establishOnCurrentThread();

                  This associates the current thread (the thread processing the HTTP request) with the locale
                  specified in the CBO Session object.

                  Creating the BoContext Object for the Web Application
                  For each request being processed, you should create a new BoContext object. After processing the
                  request, you should release the BoContext object.

                  Note: The CBO Session object is serializable but business objects are not. Any access to business objects
                  after web server failover throws an exception.


                  Handling Multiple Threads Per HTTP Session
                  In a web application, a given browser may send multiple requests to the JSP server (for example, if
                  the web application uses framesets or if the user quickly reloads the page). The JSP server may use
                  different threads to process these requests in parallel for a given session.

                  As a result, multiple requests in the same HTTP session may attempt to write to the same object at
                  the same time. For example, two requests may attempt to write to the same data in the
                  HTTPSession object, BoContext object, or business object at the same time.

                  Business objects are single-threaded objects, mainly because they hold state information. You
                  should prevent situations where different requests in the same HTTP session attempt to change the
                  state of the same business object at the same time.

                  As a general guideline, when you write your web application, you should make sure that different
                  requests in the same session do not manipulate the same instance of an object. For example, if you
                  are using a frameset of JSP pages, do not allow more than one JSP page in the frameset to access the
                  same business object instance or the same BoContext object. (In ClarifyCRM eSupport and
                  ClarifyCRM eOrder, each JSP page creates its own BoContext object for use within the page. At
                  the end of the page, the BoContext object is released.)

                  Releasing Objects After Processing an HTTP Request
                  The section Releasing Objects from Memory on page 300 explains how and why you should use the
                  release method of any unused objects to release those objects from memory (rather than relying
                  on the Java garbage collector).

                  When writing a Java web application, make sure that your application code releases the
                  BoContext object after processing a request.




310
                                                                  CHAPTER 8   Setting Up Your Application




Releasing Objects After HTTP Session Timeouts
Make sure to release any session-scoped business objects when HTTP session timeouts occur. To do
this, define your own class that implements the HttpSessionBindingListener interface. In
the definition of the valueUnbound method, invoke the release method of any unused objects.
Then create an instance of this class, and set the instance as an attribute of the HttpSession
object.

For example, suppose that you are keeping a reference to a business object (detached from any
BoContext object) in the HttpSession object. You can define the following class to implement
the HttpSessionBindingListener interface:
public class MyBoSessionListener implements HttpSessionBindingListener {

    public synchronized void valueUnbound(HttpSessionBindingEvent event)
    {
       // Release the business object.
       try
       {
          myBo.release();
       }
       catch (Exception e) {
       }
    }

    public void setMyBo(com.clarify.cbo.Base Bo)
    {
       myBo = Bo;
    }

    com.clarify.cbo.Base myBo;
}

Then, in the section of your application code that sets the business object as an attribute of the
HttpSession object, create an instance of the MyBoSessionListener class, and set this object
as an attribute of the HttpSession object.
...
// Create a new MyBoSessionListener object.
MyBoSessionListener MyBoSessionListenerObj = new MyBoSessionListener();
// Set the business object in the HttpSessionListener object.
MyBoSessionListenerObj.setMyBo(myBo);
// Set the MyBoSessionListenerObj object as an attribute of the
// HttpSession object.
session.setAttribute("MyBoSessionListenerObj", MyBoSessionListenerObj);
...

When the HTTP session times out, the object unbinds any bound objects and, as part of this
process, invokes the valueUnbound method of the MyBoSessionListener object. The
valueUnbound method releases the business object from memory.




                                                                                                     311
Writing Web Applications with ClarifyCRM Business Objects




                  Developing Web Applications with ASP
                  The following sections cover guidelines for developing CBO web applications in ASP:
                      Creating the Application Object in ASP on page 312
                      Logging In and Tracking the User’s Session in ASP on page 312

                  Creating the Application Object in ASP
                  After you create the Application object, you need to call the Application.Initialize
                  method to initialize the Application object. In a web application in ASP, you should create the
                  ClarifyCRM Application object when the Application_OnStart event occurs.

                  For example, the following example is an event handler for the Application_OnStart event in
                  ASP. This function creates the Application object and stores the object in the ASP
                  Application.Contents collection for future use.
                  function Application_OnStart() {
                     // Indicate the CBO Application object has been initialized.
                     Application.Contents("InitDone") = 0;
                     Server.ScriptTimeout = 300;
                     // Create the Clarify Application object and
                     // store it in the Application.Contents
                     // collection.
                     Application.Contents("ClfyApp") =
                        Server.CreateObject("Clarify.CBO.App.1");
                     // Initialize the Clarify Application object
                     Application.Contents("ClfyApp").Initialize();
                     // Indicate that the Application object has
                     // been initialized
                     Application.Contents("InitDone") = 1;
                  }

                  Logging In and Tracking the User’s Session in ASP
                  To log in the user, create a new Session object for the user from the Application object and
                  invoke the Session.Login method (as explained in Logging In to the Database on page 290).

                  Also, make sure to invoke the Session.SetLocale method to set the user’s locale and time zone
                  in the Session object (as explained in Setting the Locale and Time Zone on page 272.)

                  Once the user logs in, you need to keep track of the logged-in session between HTTP requests. In
                  ASP, you do this by getting the session ID from the CBO Session object and by keeping the
                  session ID in the ASP Session object. For example:
                  Session.Contents("ClfySessionId") = ClfySession.SessionId;

                  In subsequent pages, you can get the session ID from the ASP Session object, create a new CBO
                  Session object, and set the session ID in the CBO Session object to reestablish the user’s session:
                  ...
                  var ClfySession;
                  // Create a new Session object.
                  ClfySession = Application.Contents("ClfyApp").CreateSession()
                  // Get the session associated with this browser.
                  ClfySession.SessionId = Session.Contents("ClfySessionId")
                  ...



312
C H A P T E R     9




Using Global Transactions and Bulk Queries



CONTENTS
 Understanding Global Transactions and Bulk Queries 314

 Using Global Transactions 314

 Performing Bulk Queries 321

 Performing Bulk Updates with XVOs 321




                                                          313
Understanding Global Transactions and Bulk Queries




Understanding Global Transactions and Bulk Queries
                  CBO provides a number of techniques to support the ability to group queries or updates into a
                  single database request or transaction:
                     In a Java CBO application, you can define a transaction that includes updates from one or more
                     business objects and beans that use business objects (such as XBeans and save beans that you
                     create). This type of transaction is called a global transaction (a transaction that is global across
                     business objects and beans that use business objects).
                     For example, you can define a global transaction that includes updates from one or more
                     business objects, custom save beans, and custom XBeans. If any update in the transaction fails,
                     none of the updates are committed to the database. For details on global transactions, see Using
                     Global Transactions on page 314.
                     In a Java CBO application, you can perform bulk queries by using the BoContext.query
                     method to query all business objects or a subset of the business objects attached to the
                     BoContext object.
                     For detail, see Performing Bulk Queries on page 321.
                     Finally, if you are need to update the database with data in XVOs, and if the update operation
                     does not require business logic, you can use XVOs instead of business objects to update the
                     database.
                     To perform the update, call the BoContext.updateAll method and pass in the XVOs as an
                     array. The BoContext.updateAll method commits the data in the XVOs to the database. For
                     details on this technique, see Performing Bulk Updates with XVOs on page 321.

                     Important: Because this XVOs do not implement any business logic, using this signature of the
                     BoContext.updateAll method just saves the specified data to the database. You should not use this
                     technique for any tables that have specific types of business objects. For details, see Performing Bulk
                     Updates with XVOs on page 321.




Using Global Transactions
                  A global transaction is a transaction that commits changes from one or more business objects,
                  XBeans, and save beans.

                  In a global transaction, you use the BoContext object to define the boundaries of the transaction.
                  You start a transaction by calling the beginTransaction method, and you commit the
                  transaction by calling the commitTransaction method.

                  Between these calls, any calls to the BoContext.updateAll method (including calls made by
                  save beans or XBeans) do not commit the updates to the database. Instead, the
                  BoContext.updateAll method prepares the updates. When you call the commitTransaction
                  method, these updates are all committed as part of the same transaction.

                  Note: The BoContext.updateAll method has signatures that pass in XVOs containing the data to
                  save. You cannot use these signatures in a global transaction.




314
                                                         CHAPTER 9   Using Global Transactions and Bulk Queries




For example, the following section of code commits the changes made by Generic business
objects, a save bean, and an XBean in the same transaction:
   ...
   // Set the starting boundary for the transaction.
   boContext.beginTransaction();
   ...
   // Prepare the updates for all business objects created from boContext.
   boContext.updateAll();
   ...
   // Prepare the updates for this save bean, which was created from
   // the same BoContext object (boContext).
   mySaveBean.exec();
   ...
   // Prepare the updates for this XBean.
   myXBean.execute(boContext);
   ...
   // Commit all the above updates to the database in a single transaction.
   boContext.commitTransaction();
   ...

In this example, the exec method of mySaveBean calls m_ClfyForm.updateAll to save
changes. The execute method of myXBean calls the executeImpl method, which in turn calls
the BoContext.updateAll method to save changes. In both cases, the methods prepare the
changes but do not commit the changes to the database. Instead, the commitTransaction
method commits all changes for the business objects, save bean, and XBean. The changes are
committed in a single transaction.


Understanding Enlistable Beans
If you want to use an XBean or save bean in a global transaction, the bean must be designed to
support global transactions. These beans are called enlistable, since the beans can enlist in a global
transaction.

Note: "Enlistable” does not mean that the bean can only be used in a global transaction. An enlistable bean
can also be used outside a global transaction.


Enlistable beans implement the com.clarify.cbo.Enlistable interface.

For the specific rules that determine if a bean is enlistable, see Designing Beans to Support Global
Transactions on page 316.


Understanding Nonenlistable Beans
A nonenlistable bean is a bean that updates the ClarifyCRM database but cannot participate in a
global transaction.

Nonenlistable beans implement the com.clarify.cbo.NonEnlistable interface.

An example of a nonenlistable bean is an XBean that relies on querying for the results of a database
update in order to perform a subsequent database update. In this example, the first and second
database updates cannot be part of the same transaction, so the XBean cannot enlist in a global
transaction.


                                                                                                           315
Using Global Transactions




                  Nonenlistable beans set their own transaction boundaries. By definition, a bean that calls
                  BoContext.beginTransaction and BoContext.commitTransaction (to set its own
                  transaction boundaries) is nonenlistable, since it cannot participate in another transaction.

                  For example, if the executeImpl method in an XBean calls BoContext.beginTransaction
                  and BoContext.commitTransaction, this XBean is nonenlistable. The XBean defines its own
                  transaction boundaries, so the bean cannot be enlisted in another transaction.

                  If a BoContext object is in a global transaction and you pass this object to the execute method of
                  a nonenlistable XBean, the XBean base class creates a separate, private BoContext object to pass to
                  the preExecute, executeImpl, and postExecute methods of the XBean.

                  This separate, private BoContext object is not part of the global transaction. When you call the
                  updateAll method of this BoContext object, the updates are committed to the database
                  immediately.

                  If you are writing a nonenlistable, old-style integration bean (a bean that extends the base class
                  com.clarify.cbo.IntegrationBeanImpl), the base class does not check if the BoContext
                  object is in a global transaction. To avoid problems if the BoContext object is in a global
                  transaction, you should manually create a separate, private BoContext object to commit your
                  changes.

                  Note: The old-style integration beans are supported for backward compatibility only. If you are writing a new
                  integration bean, you should write an XBean (a class that extends the com.clarify.cbo.XBeanImpl
                  base class) and that implements the com.clarify.cbo.Public interface.



                  Understanding Query Beans
                  Any bean that does not implement either the Enlistable or NonEnlistable interface is
                  considered to be a component used for querying only.


                  Designing Beans to Support Global Transactions
                  In order to enlist in a global transaction, a bean must follow these rules:
                      Enlistable beans must implement the com.clarify.cbo.Enlistable interface. Beans that
                      implement this interface indicate that they are designed to participate in global transactions.
                      An XBean is enlistable only if all of its methods support global transactions and follow the rules
                      in this section.
                      In your enlistable bean, you cannot create and use any nonenlistable beans. Any child beans
                      that you create must comply with the rules listed in this section.
                      In your enlistable bean, you must not call the BoContext.beginTransaction and
                      BoContext.commitTransaction methods.
                      In your enlistable bean, you must not call the following methods:
                      – BO.update




316
                                                      CHAPTER 9   Using Global Transactions and Bulk Queries




– BO.updateAll
If these methods are called within a transaction, a runtime exception is thrown.
Instead of calling these methods, call the BoContext.updateAll method. For example, in an
enlistable save bean, you call m_ClfyForm.updateAll.
This BoContext.updateAll method does the following:
  If called in a global transaction, the BoContext.updateAll method prepares the updates
  to be made but does not commit the changes. The BoContext.commitTransaction
  method commits the changes.
  If called outside of a global transaction, the BoContext.updateAll method prepares the
  updates and commits the changes.
  In an enlistable XBean used outside of a global transaction, the changes are committed at the
  end of the execute method of the XBean, after the postExecute method is called. For
  more information on XBeans and global transactions, see Setting Up an XBean to Enlist in
  Global Transactions on page 399.

Note: The BoContext.updateAll method has signatures that pass in XVOs containing the data to
save. You cannot use these signatures in a global transaction.

In your enlistable bean, do not query a business object participating in the transaction after the
BoContext.updateAll method is called. The data in the business object is not committed
until BoContext.commitTransaction is called, and performing a query on a business
object clears out any existing rows in the rowset.
In your enlistable bean, if you need to perform a bulk query, use the signature of the
BoContext.query method that passes in an array of business objects. For example:
   // Perform a bulk query of m_boCase, m_boContact, and m_boSite
   boContext.query(new Base[] {m_boCase, m_boContact, m_boSite});

Important: The specified business objects must be attached to the current BoContext object (for
example, you can pass in business objects that were created from the current BoContext object).

If you are working with a save bean, you can call this method from the m_ClfyForm member
variable:
   // Perform a bulk query of m_boCase, m_boContact, and m_boSite
   m_ClfyForm.query(new Base[] {m_boCase, m_boContact, m_boSite});
Do not use the signature that takes no arguments:
   // Do NOT call this method in an enlistable bean
   boContext.query();
Performing a bulk query without specifying individual business objects causes a query of all
business objects created from the BoContext object. This includes all of the other business
objects that participate in the transaction.
In an save bean, you must not release the FormContext object represented by the
m_ClfyForm member variable.
In a global transaction, m_ClfyForm represents the FormContext object that holds all changes
to be committed in the global transaction. (The FormContext class is the superclass of the
BoContext class and serves the same purpose as BoContext in a global transaction.)


                                                                                                        317
Using Global Transactions




                      Similarly, in an XBean, you must not release the BoContext object passed in to the
                      preExecute, executeImpl, or postExecute methods.
                      If you want to prevent individual business objects from participating in the global transaction,
                      you can set the BO.UpdateMode property of those business objects to
                      CboConstants.cboSubmitDisabled and CboConstants.cboSubmitBlocked.

                  When writing an enlistable bean, note the following important caveats:
                      In the business objects created from the BoContext object, any data that you change is
                      committed to the database when BoContext.commitTransaction is called.
                      You should not set any data that you do not want committed in any business objects created
                      from that BoContext object. If a business object contains any unsaved changes that you do not
                      want committed with the global transaction, set the BO.UpdateMode property to
                      cboSubmitBlocked for that business object.
                      When you call BoContext.updateAll, changes are not committed immediately. Changes are
                      committed only when BoContext.commitTransaction is called.
                      As a result, when writing your bean, you should not expect the results of the update to be
                      available when you call BoContext.updateAll.

                  This caveat may have an effect on how to implement your bean. Because BoContext.updateAll
                  does not actually commit the changes, you should keep the following expectations in mind:
                      After calling BoContext.updateAll, do not expect subsequent database queries to retrieve
                      the updated data.
                      Do not expect the BoContext.updateAll call to indicate the success or failure of the update
                      operation, since the update is not actually performed when you call that method.
                      For example, do not expect BoContext.updateAll to throw an exception if a cardinality
                      constraint is not met or if there is a unique index violation.
                      Do not expect the business object fields to be updated with values from the database, such as:
                      – the actual value in the database for a field that you incremented or appended to (by calling
                        the Field.incr or Field.append method)
                      – the actual value in the database for a field containing a timestamp
                      If you are providing a getter method to return the value of one of these fields, do not get a copy
                      of the field value and return the copy. The value in the business object might not be updated
                      when you get the value of the copy. Instead, define the getter method to return the value
                      directly from the business object.
                      By default, the actual object ID number for a new row is not generated until the new row is
                      committed to the database. (As explained in Adding a New Row to the Rowset on page 70, a
                      temporary object ID is generated.) The new row is not saved until the entire global transaction is
                      committed.
                      In addition, for fields that use numbering schemes (such as the id_number field in the case
                      table, which uses the Case ID numbering scheme), ID numbers for new rows are not generated
                      until the new row is committed.
                      You can change this default behavior and set up a business object to generate the object ID and
                      ID numbers when you call the BO.addNew method. In this way, you can make these values
                      available before the global transaction is committed. For details, see Generating the Object ID and
                      ID Number for a New Row on page 71.


318
                                                      CHAPTER 9   Using Global Transactions and Bulk Queries




Setting Up a Global Transaction
If you are using XBeans through EJBs, the EJB layer automatically sets up and commits global
transactions for any enlistable XBeans. For details, see the Integration Gateway Implementation Guide.

You can also set up and commit a global transaction from your own application code. Note that if
you begin and commit a global transaction in a save bean or an XBean, that save bean or XBean is
nonenlistable. These beans cannot participate in any other global transactions.

To use a global transaction, do the following:
1. Call the BoContext.beginTransaction method to begin the transaction.
   Make sure that your code following the beginTransaction call follows the rules covered in
   Designing Beans to Support Global Transactions on page 316.
   While these rules are written for enlistable beans, the rules also apply to any application code
   between the beginTransaction and commitTransaction calls. For example, between the
   beginTransaction and commitTransaction calls, do not call the BO.update or
   BO.updateAll methods.
2. Create and set up your business objects, save beans, and XBeans.
   When you create business objects and save beans, make sure that you use the same BoContext
   object that defines the transaction boundaries:
     For individual business objects, call the createBO or createGenericBO methods of that
     BoContext object.
     For save beans, call the createBean method of that BoContext object.
     This sets the BoContext object as the m_ClfyForm member of the save bean. In the save
     bean, you can use m_ClfyForm to create new business objects that should participate in the
     global transaction.
   For XBeans, you pass that BoContext object to the XBean when calling the execute method
   (see Step 3).
3. Prepare the updates to be committed as part of the transaction. For example:
     For business objects, call the BoContext.updateAll method on the BoContext object that
     defines the transaction boundaries.
     For save beans, call the exec method.
     For XBeans, call the execute method, passing in the BoContext object that defines the
     transaction boundaries.
     In the preExecute method, use the BoContext object that is passed into the method to
     create any business objects that should participate in the global transaction.
     In the executeImpl method, call the BoContext.updateAll method on the BoContext
     object that is passed in.

   Note: After you call BoContext.updateAll, the save bean exec method, and the XBean
   execute method, the changes are not yet committed to the database. See Designing Beans to Support
   Global Transactions on page 316 for caveats regarding this.

4. To commit the transaction, call the BoContext.commitTransaction method.



                                                                                                        319
Using Global Transactions




                  5. If you need to get the values of any updated fields, get the field values.
                      For example, if you need to query the database for updated values, you can perform the query
                      at this point because the data has been committed.
                      At this point, you can also get the updated values of business object fields containing
                      timestamps or values that you have incremented or appended to.
                  6. Release any business objects and beans that you no longer need.

                  The following sample code sets up and commits a global transaction. The example creates and uses
                  a save bean, XBean, and a Generic BO to make the changes in this transaction.
                      import com.clarify.cbo.*;
                      import com.clarify.xvobase.*;
                      import com.clarify.xvo.*;
                      import com.clarify.webframework.*;
                      import com.mycompany.xbeans.custom.*;
                      import com.mycompany.web.custom.*;
                      ...
                      // Start the transaction boundary
                      boContext.beginTransaction();
                      // Create any save beans, XBeans, and business objects needed
                      genBo = boContext.createGenericBO("x_custom_table");
                      saveBean = (MySaveBeanSB) boContext.createBean(
                           "com.mycompany.web.custom.MySaveBeanSB");
                      xBean = (MySaveXB) boContext.createBean(
                           "com.mycompany.xbeans.custom.MySaveXB");
                      // Use the business objects, save beans, and XBeans to
                      // prepare the updates.
                      genBo.addNew();
                      genBo.setValue("x_field_1", strField1Value);
                      genBo.setValue("x_field_2", strField2Value);
                      boContext.updateAll();
                      saveBean.setMyCdo(myCdo);
                      saveBean.init();
                      saveBean.exec();
                      xBean.setMyXvo(myXvo);
                      xBean.execute(boContext);
                      // Commit the updates in a transaction.
                      boContext.commitTransaction();
                      // After committing the transaction, you can get any updated data needed
                      // from the database, business objects, or beans.
                      strNewObjid = saveBean.getNewObjid();
                      ...
                      // Release any business objects and beans that you no longer need
                      genBo.release();
                      saveBean.release();
                      ...
                      // If you need to set up another transaction on the same BoContext
                      // object, you can call beginTransaction at this point.
                      ...




320
                                                                CHAPTER 9   Using Global Transactions and Bulk Queries




Performing Bulk Queries
        If you want to perform a bulk query, you can use one of the following signatures of the
        BoContext.query method:
           To perform a bulk query of a subset of business objects, call the BoContext.query method
           and pass in an array of the business objects to query.
           This method queries only the specified business objects. Any other business objects are not
           affected by the query.

           Important: The specified business objects must be attached to the current BoContext object (for
           example, you can pass in business objects that were created from the current BoContext object).

           For example, the following statement performs a bulk query of the business objects represented
           by the variables m_boCase, m_boContact, and m_boSite:
           // Perform a bulk query of m_boCase, m_boContact, and m_boSite
           boContext.query(new Base[] {m_boCase, m_boContact, m_boSite});
           To perform a bulk query of all business objects created from a BoContext object, call the
           BoContext.query method, passing in no arguments.

           Important: You should not use this signature in any component that can participate in a global
           transaction (see Using Global Transactions on page 314). This signature queries all business objects
           created from the BoContext, including business objects created by all other components participating
           in the global transaction. For example, suppose that three XBeans participate in a global transaction.
           Using this signature on the BoContext object for the global transaction queries all business objects
           created by all three XBeans from that same object.

           The following statement performs a bulk query of all business objects created from the
           BoContext object represented by the variable boContext:
           // Perform a bulk query of all business objects created from boContext
           boContext.query();

        If you want to prevent individual business objects from participating in the bulk query, you can set
        the BO.QueryMode property of those business objects to CboConstants.cboSubmitDisabled
        and CboConstants.cboSubmitBlocked.

        If any of the queried business objects is a parent of a child business object, the child business object
        is also included in the bulk query. For more information on how query and update requests affect
        parent and child business objects, see Controlling Queries and Updates in Child Objects on page 196.




Performing Bulk Updates with XVOs
        If you need to update the database with data in XVOs, and if the update operation does not require
        any business logic, you do not need to create or use business objects to save the changes.

        Instead, you can call the BoContext.updateAll method and pass in the XVOs as an array. The
        BoContext.updateAll method internally creates Generic business objects for the XVOs,
        copies data from the XVOs to these business objects, and uses the Generic business objects to save
        the changes. You do not need to create the business objects yourself.


                                                                                                                  321
Performing Bulk Updates with XVOs




                 Important: Do not use this technique to commit changes to tables that have specific types of business objects,
                 such as the case table and the contact table. When updating these types of tables, always use the
                 corresponding type of business object (such as the Case BO or the Contact BO) to save the changes.


                 The BoContext.updateAll method resolves the XVOs (so that you do not need to call
                 BoContext.resolveLookups before passing the XVOs to this method) and performs a bulk
                 update of the database.

                 Note: If you are specifying an object ID to identify the row to update (see Identifying a Row by Object ID on
                 page 147), make sure that the object ID is valid and corresponds to an existing row in the
                 ClarifyCRM database. If no row exists with the specified object ID, the BoContext.updateAll method
                 does not throw an exception. Instead, no updates are made to the ClarifyCRM database.


                 In a single transaction, this method commits the changes in the XVOs and in any business objects
                 created from this BoContext object, including business objects created from save beans that are
                 created from this BoContext object.

                 Important: Before calling the BoContext.updateAll method, make sure that you are ready to commit
                 any changes in any business objects created from this BoContext object directly or created through a save
                 bean that was created from this BoContext object.


                 For example, suppose that you use a BoContext object to create a Case business object and a
                 MySaveBeanSB save bean. The MySaveBeanSB save bean uses this BoContext object to create
                 business objects. When you call the BoContext.updateAll method and pass in an array of
                 XVOs, this method commits the changes in the XVOs, the Case business object, and the business
                 objects created by the MySaveBeanSB save bean. All of these changes are committed in a single
                 transaction.

                 Important: The BoContext.updateAll method signatures that pass in an array of XVOs are not
                 supported for use within a global transaction. (For information on global transactions, see Using Global
                 Transactions on page 314.)


                 The following sections explain how to use this technique in more detail:
                     Adding or Updating Rows on page 322
                     Establishing and Removing Relations on page 324
                     Deleting Rows on page 326


                 Adding or Updating Rows
                 If an XVO contains a new row or updates to an existing row, you can set up the XVO in an array
                 and pass the array to the BoContext.updateAll method.

                 To identify the existing rows to update, you can use the lookup criteria explained in Using an XVO
                 to Identify an Existing Row on page 146.




322
                                                           CHAPTER 9   Using Global Transactions and Bulk Queries




Important: If you are using an object ID as the lookup criteria, note that the updateAll method does not
query the database to verify that the specified object ID exists. Before you pass in an XVO that uses an object
ID as the lookup criteria, make sure that the object ID is valid.


The following example updates the database with data from an XVO for a new row and an XVO
with updates to an existing row.
   // Adding and updating rows by directly using XVOs.
   // In this example, no relations are set or removed, so null is passed
   // as the second argument.
   Xvo[] updatedXvoArray = boContext.updateAll(
        new Xvo[] {myNewVo, myUpdatedVo}, null);

In the array of XVOs, the XVOs do not need to belong to the same Java class. The XVOs can also be
a mixture of XVOs for new rows and XVOs for existing rows.

If you need to establish or remove relations as part of the update, you can pass in a RelateSet
object as the second argument of the updateAll method (see Establishing and Removing Relations
on page 324). Otherwise, if you do not need to update any relations, you can pass a null for the
second argument.

The method returns an array of XVOs corresponding to the XVOs that you passed in. These
returned XVOs only contain the object IDs of the rows. The rest of the fields in returned XVOs are
empty.

For example, suppose that when you call the BoContext.updateAll method, you pass in an
array of XVOs. In the array, if the first XVO is an AddressVo for a new address, the first XVO in
the returned array is an AddressVo with the object ID of that newly added address.

If you are using XVOs to add new rows, you can get the newly assigned object IDs from the
returned array of XVOs.

Note: Since the BoContext.updateAll method returns an array of elements of the base class
com.clarify.xvobase.Xvo, you must cast each element as the specific type of XVO (for example,
SiteVo or ContactVo) before calling the getObjid method to get the object ID from the XVO.


The following example sets up three XVOs for different custom tables A, B, and C in the database.
Two of the XVOs represent new rows in tables A and B, while the third XVO contains updates to an
existing row in table C. The existing row is identified by a unique index named MyIndex, which is
based on ID numbers in table C.
   import com.clarify.xvo.*;
   ...
   // Set up an XVO for adding a new row to the table A.
   AVo myNewAVo = new AVo(true);
   myNewAVo.setXMyField(strMyFieldValue);
   ...
   // Set up an XVO for adding a new row to the table B.
   BVo myNewBVo = new BVo(true);
   myNewBVo.setXMyField2(strMyFieldValue2);
   ...
   // Set up an XVO for updating a row to the table C.
   CVo myUpdateCVo = new CVo();
   CLookupByMyIndex myLookup =



                                                                                                             323
Performing Bulk Updates with XVOs




                          new CLookupByMyIndex(strID);
                     myUpdateCVo.setLookupByMyIndex(myLookup);
                     myUpdateCVo.setXMyData(strMyData);
                     ...

                 The following example uses the BoContext.updateAll method to save the changes in these
                 three XVOs, creating two new rows for the first two XVOs, and updating data in the third row.
                 Note the following in this example:
                     No relations are set or removed, so null is passed for the second argument to the
                     BoContext.updateAll method.
                     Since the BoContext.updateAll method internally resolves the object IDs of the XVOs for
                     existing rows, there is no need to call BoContext.resolveLookups before passing the XVOs
                     to the updateAll method.

                 The example uses the returned XVOs to get the newly assigned object IDs for the new rows added
                 to the database.
                     import com.clarify.xvobase.*;
                     ...
                     // Save the data in the XVOs to the database.
                     Xvo[] returnedXvoArray = boContext.updateAll(
                          new Xvo[] {myNewAVo, myNewBVo, myUpdateCVo}, null);

                     // Get the object IDs of the newly created rows
                     String strNewAObjid = ((AVo) returnedXvoArray[0]).getObjid();
                     String strNewBObjid = ((BVo) returnedXvoArray[1]).getObjid();
                     ...


                 Establishing and Removing Relations
                 You can also use the BoContext.updateAll method to make the following changes to relations
                 between rows:
                     You can establish a relation between two rows.
                     You can remove a relation between two rows.
                     If a given row is related to one or more rows through an OTM or MTM relation, you can remove
                     that relation from the given row to all related rows.




324
                                                    CHAPTER 9   Using Global Transactions and Bulk Queries




To perform these operations, do the following:
1. Create a com.clarify.xvobase.RelateSet object to specify the changes that you want to
   make.
2. Call the following methods of the RelateSet object to specify the relations to establish or
   remove:
     To establish a relation between two rows, call the relate method, passing in XVOs for the
     two rows and the name of the relation to establish.
     For example, the following statement sets up the myRelateSet object to establish the
     relation A2B between the two rows represented by the XVOs myAVo and myBvo:
      // Setting up the A2B relation between the XVOs myAVo and myBVo.
      myRelateSet.relate(myAVo, myBVo, "A2B");
     To remove a relation between two rows, call the unrelate method, passing in XVOs for the
     two rows and the name of the relation to remove.
     For example, the following statement sets up the myRelateSet object to remove the relation
     A2B between the two rows represented by the XVOs myAVo and myBvo:
      // Removing the A2B relation between the XVOs myAVo and myBVo.
      myRelateSet.unrelate(myAVo, myBVo, "A2B");
     To remove a relation from one row to all related rows, call the unrelateAll method,
     passing in an XVO for the row and the name of the relation to remove.
     For example, the following statement sets up the myRelateSet object to remove the relation
     A2B from the row represented by the XVO myAVo to all rows in table B:
      // Removing the A2B relation from the XVOs myAVo to all related
      // rows.
      myRelateSet.unrelateAll(myAVo, "A2B");
   For the relate and unrelate methods, you can either pass in the relation name or the inverse
   relation name. For example, in the previous statements calling the relate and unrelate methods
   above, you can pass in the inverse relation name instead:
      // For the relate and unrelate methods, you can pass in the
      // relation name or the inverse relation name.
      myRelateSet.relate(myAVo, myBVo, "B2A");
      myRelateSet.unrelate(myAVo, myBVo, "B2A");
   For the unrelateAll method, you should specify the relation that starts from the table
   represented by the XVO that you are passing in.
3. Call the BoContext.updateAll method, passing in the RelateSet object as the second
   argument.
   For the XVOs that you are using to establish or remove relations, you do not need to pass these
   XVOs in the first argument unless the XVOs represent new rows that need to be saved to the
   database.

You can call the relate, unrelate, and unrelateAll methods multiple times for a RelateSet
object. The RelateSet object just collects the changes to be made.

The following example operates on three custom tables named A, B, and C. There is a relation
named A2B (the inverse name is B2A) between the tables A and B, and there is a relation named
B2C (the inverse name is C2B) between tables B and C.




                                                                                                      325
Performing Bulk Updates with XVOs




                 This example performs the following operations:
                     Creates a new row in A (represented by the XVO myNewAVo) and establishes the relation A2B to
                     a row in B (represented by the XVO myB1Vo).
                     Removes the B2C relation between a row in B (represented by the XVO myB2Vo) and a row in C
                     (represented by the XVO myCVo).
                     For a row in B (represented by the XVO myB3Vo), removes the B2C relation to all rows in C.

                 The following section of code creates and sets up a RelateSet object with these operations and
                 calls the BoContext.updateAll method to perform the operations:
                     import com.clarify.xvobase.*;
                     import com.clarify.xvo.*;
                     ...
                     // Create the RelateSet object to collect the operations to be
                     // performed.
                     RelateSet myRelateSet = new RelateSet();
                     // Set up the A2B relation between the new row in table A (myNewAVo)
                     // and the existing row in table B (myB1Vo).
                     myRelateSet.relate(myNewAVo, myB1Vo, "A2B");
                     // Remove the B2C relation between an existing row in table B (myB1Vo)
                     // and an existing row in table C (myCVo).
                     myRelateSet.unrelate(myB2Vo, myCVo, "B2C");
                     // Remove the B2C relation from an existing row in table B (myB3Vo)
                     // to all related rows in table C.
                     myRelateSet.unrelateAll(myB3Vo, "B2C");
                     // Commit the changes. Since myNewAVo represents a new row, you must pass
                     // this XVO in an array in the first argument.
                     Xvo[] returnedXvoArray = boContext.updateAll(new Xvo[] {myNewAVo},
                          myRelateSet);
                     ...


                 Deleting Rows

                 Important: Many baseline tables in the ClarifyCRM database contain data that should never be deleted (for
                 example, because the data represents an audit trail or has relations to other rows). Although this section
                 explains how to delete rows in tables, you should use this functionality with extreme caution.


                 You can delete rows in the database by calling the BoContext.updateAll method and passing
                 in an array of the XVOs for those rows as the third argument:
                     // Using BoContext.updateAll to delete rows from the database
                     boContext.updateAll(null, null, new Xvo[] {deleteVo1, deleteVo2, ...});

                 Unlike the XVOs that you pass in the first argument (the XVOs representing rows to add or
                 update), the BoContext.updateAll method does not return any XVOs corresponding to the
                 rows that you are deleting.

                 If you also want to perform updates and relation changes, you can pass the XVOs and the
                 RelateSet object for those changes as the first and second argument.

                 Note: This technique does not delete any external IDs for the deleted rows. You must delete the external IDs
                 in a separate operation. For details on external IDs, see the Integration Gateway Implementation Guide.



326
C H A P T E R     1 0




Handling Errors



CONTENTS
 Overview: Error Handling in the Business Objects 328

 Handling Errors in ActiveX 328

 Handling Errors in Java 332

 Recovering from Database Update Failures 336

 Adding Your Error Code to the string_db Table 337




                                                        327
Overview: Error Handling in the Business Objects




Overview: Error Handling in the Business Objects
                  In most situations, when an error occurs in a business object, information about the error is placed
                  in the ClarifyCRM Error object. (In Java, the class is named CboError. For the purposes of this
                  chapter, the term Error object identifies an object of this class.)

                  The business objects associated with the same Session object share the same ClarifyCRM Error
                  object. You can get the Error object either through the BO.Error property or the BoContext/
                  FormContext Error property.

                  Error information is also copied in to the error object of the scripting environment, and the error is
                  raised at the scripting level:
                      In Visual Basic, VBScript, or VBA, error information is copied to the Number, Source, and
                      Description properties of the Err object.
                      In JavaScript, error information is copied to the number and description properties of the
                      JavaScript Error object.
                      In Java, the information about the error is placed in to the ClarifyCRM CboError object, and
                      the object is thrown. (Unlike the other scripting languages, there is no general error object for
                      the scripting environment.) Since the CboError class extends RuntimeException, make sure
                      to use try...catch loops to check for errors.
                      Note that if the error occurs before the ClarifyCRM Session object is created, an Exception is
                      raised instead.

                  For more information on handling errors, see the following sections:
                      Handling Errors in ActiveX on page 328
                      Handling Errors in Java on page 332
                      Recovering from Database Update Failures on page 336
                      Adding Your Error Code to the string_db Table on page 337




Handling Errors in ActiveX
                  When an error originates from a CBO, the error information is placed in to an instance of the
                  ClarifyCRM CboError object and the error is thrown.

                  The following sections explain how to handle errors thrown in an ActiveX implementation:
                      Checking the Scripting Environment’s Error Object on page 329
                      Using the ClarifyCRM Error Object With ActiveX on page 329
                      Adding Errors to the Error Object in ActiveX on page 330
                      Passing Arguments to the Error Message on page 331
                      Example of Adding a Custom Error on page 331




328
                                                                              CHAPTER 10   Handling Errors




Checking the Scripting Environment’s Error Object
Visual Basic, VBScript, VBA, and JavaScript have a common error object for the scripting
environment.

In Visual Basic, VBScript, and VBA, the Err.Number property holds a 16-bit error code (a value
between 0 and 65536). In the ClarifyCRM Error object, the error code is a 32-bit integer. Because of
this difference in the size of error codes, the ClarifyCRM error code cannot be copied in to the
Err.Number property.

Instead, the Err.Number property is set to 1001 when a ClarifyCRM error occurs. If you want to
get the actual ClarifyCRM error number, you need to check the ClarifyCRM Error.Number
property.

Similarly, when error information is copied to the JavaScript Error object, the part of the error
number that represents the error code is set to 1001. (The JavaScript Error.Number property
holds a 32-bit value. The upper 16 bits represents a facility code, while the lower 16 bits represents
the actual error code. To get the error code from the JavaScript Error.Number, mask out the top 16
bits.)

The following section of JavaScript code demonstrates how to check for ClarifyCRM errors:
try {
   // Your code here.
   ...
} catch(e) {
   // Mask out the top 16 bits and determine if
   // the error code indicates a Clarify error.
   if ((e.number & 0xFFFF) == 1001) {
      // Get information from the Clarify Error object.
      ...
   }
   ...
}


Using the ClarifyCRM Error Object With ActiveX
In ActiveX, use the Error.IsEmpty method to determine if the business objects have set error
information in the Error object. If this method returns True, you can get more information on the
error by checking the following properties of the Error object:
   Error.Number
   This property specifies the error number of the top-most error (the error of the highest-level
   process). If the Error object is empty, the value of this field is 0x8900008 (not 0). If you want to
   determine if an error has occurred, you should use the Error.IsEmpty method instead of
   checking the value of the Error.Number property.
   Error.Description
   This property specifies the description of the top-most error.
   Error.CompleteInfo
   This property specifies the complete chain of errors, including the number and description of
   the top-most error.




                                                                                                      329
Handling Errors in ActiveX




                   The following section of JavaScript code determines if an error has occurred:
                   ...
                   var strErrorInfo;
                   ...
                   try {
                      // Your code here.
                      ...
                   } catch(e) {
                      if (((e.number & 0xFFFF) == 1001) &&
                         (! ClfyForm.Error.IsEmpty())){
                         // Get information from the Clarify Error object.
                         strErrorInfo = ClfyForm.Error.CompleteInfo;
                      } else {
                         // Error information is not available in the
                         // Clarify Error object, so get the error information
                         // from the JavaScript Error object.
                         strErrorInfo = "Error: " + (e.number & 0xFFFF);
                         strErrorInfo += "\nDescription: " + e.description;
                      }
                   }
                   ...

                   For a listing of the ClarifyCRM error codes, see the Core Business Objects Reference Guide.


                   Adding Errors to the Error Object in ActiveX
                   To set your own error codes and messages in the ClarifyCRM Error object, do the following:
                       First, you need to add your error codes and messages to the string_db table. For information
                       on adding your own error information to the string_db table, see Adding Your Error Code to the
                       string_db Table on page 337.
                       Next, you need to set your error code in the ClarifyCRM Error object.

                   To set your error code in the ClarifyCRM Error object, use the Error.AddError method:
                   Error.AddError(calculated_error_number, source_of_error [, arguments]);

                   Pass the following arguments to this method:
                       calculated_error_number is an error number calculated by adding the base error number
                       of the subsystem with the ID of the error code in the string_db table. Since the base error
                       number for user-defined errors is 113246208 (or 0x06C00000 in hexadecimal), you must use the
                       following algorithm:
                       113246208 + (error_code - 25600)
                       error_code is the ID of the error code (in the string_db table) that you want to add to the
                       Error object.
                       source_of_error is a string describing the source of the error. This string appears in the error
                       message returned by the ClarifyCRM Error.Description and Error.CompleteInfo
                       properties:
                       Source: source_of_error
                       Error Code: calculated_error_number
                       Message: string_from_string_db
                       arguments is described in Passing Arguments to the Error Message on page 331.


330
                                                                             CHAPTER 10   Handling Errors




If the Error object already contains an error, the new error is added to the front of the error chain,
and the Error.Description property returns information only on the newly added error.

To get information on errors specified in the Error object before you added the error, get the
Error.CompleteInfo property, which returns the entire chain of errors:
Source: source_of_new_error
Error Code: calculated_new_error_number
Message: string_from_string_db
----------
Source: source_of_existing_error
Error Code: existing_error_number
Message: existing_error_message
...


Passing Arguments to the Error Message
If you want to fill in parts of the error message in the Error object from your script code, you can
specify the placeholders %1 through %5 in the message in string_db.

When you call the Error.AddError method, you can pass additional arguments after the source
name.

For example, suppose that your custom error message is:
"Module Name: %1, Section Name: %2"

When you add this error to the Error object, you can specify the module name and section name as
additional arguments after the source name:
Error.AddError(113246208, strSourceName, strModuleName, strSectionName);


Example of Adding a Custom Error
Suppose that you import the following dataex record in to the string_db table:
OBJECT TYPE="string_db", NAME="string_25600" UNIQUE_FIELD=id , locale
   FIELDS
      id=25601;
      string="Error in web application %1 on server %2.";
      name=ANOTHER_CUSTOM_ERR;
      locale=0;
      type=0;
   END_FIELDS
   RELATIONS
   END_RELATIONS
END_OBJECT NAME="string_25600"

In order to add this custom error to the ClarifyCRM Error object in JavaScript, you can use the
following section of code:
...
var nOriginalCode = 25601;
var nErrorCode = 113246208 + (nOriginalCode - 25600);
ClfyForm.Error.AddError(nErrorCode, strSourceName, strWebAppName,
   strServerName);
...




                                                                                                     331
Handling Errors in Java




Handling Errors in Java
                  In CBO, the CboError class represents an error. The CboError class derives from the Java
                  RuntimeException class.

                  When an error originates from CBO, error information is placed in a new instance of the CboError
                  class, and the CboError object is thrown as an exception. In your application code, you can catch
                  this type of exception and extract information from the object to determine what happened.

                  You may also want to create your own exception classes for your application code. You can use the
                  CboError class as a base class and derive your exception classes from CboError.

                  The following sections explain how to use the CboError class for handling errors:
                      Handling a CboError Exception on page 332
                      Deriving Exception Classes from CboError on page 333


                  Handling a CboError Exception
                  When you catch an exception that is a thrown CboError object, you can use the following
                  CboError methods to get more information about the error:
                      getStringId
                      This method returns the error identifier.
                      getNumber
                      This method returns the error number. (For a listing of the error numbers for errors thrown by
                      CBO, see the Core Business Objects Reference Guide.)
                      getSource
                      This method returns a string containing the source of the error.
                      getDescription
                      This method returns the error message string.
                      getCompleteInfo
                      This method returns the identifier, number, source, and message for each error in a chain of
                      errors, as well as a stack trace.




332
                                                                              CHAPTER 10   Handling Errors




The following section of Java code demonstrates a sample error handler that looks for errors
thrown by the business objects and then for general exceptions.
import com.clarify.cbo.*;
...
try {
   ...
} catch(CboError e) {
   // Caught error thrown by a business object.
   String strErrorMsg = "Error: " + e.getNumber() + " " +
      e.getDescription();
   System.out.println(strErrorMsg);
} catch(Exception e) {
   // Get and print information about other exceptions.
   String strErrorMsg = "Error: " + e.getMessage();
   System.out.println(strErrorMsg);
} finally {
   // Clean up the business objects.
   ...
}


Deriving Exception Classes from CboError
In Java, you create your own exception classes by subclassing an existing exception class. If you
want to define your own exception classes for your CBO applications, you can derive from the
CboError class.

One advantage to subclassing is that you can add a catch block for CboError exceptions that will
catch both CBO exceptions and exceptions thrown by your application code.

The CboError class provides four constructors:
   Two constructors allow you to read in the error message from a string in the string_db table
   (see Reading the Error Message from the string_db Table on page 333).
   Two constructors allow you to specify the error message as an argument (see Specifying the Error
   Message in the Constructor on page 335).

Both constructors also allow you to pass in an existing CboError object to “chain” exceptions
together. For example, if you are handling a CboError exception thrown by CBO, you can chain
this error to your own error. Application code can invoke the getCompleteInfo method of your
exception class to access the original CboError exception that caused the problem.

For the constructors that allow you to specify an error message as an argument, you must also pass
in an error code. In general, you should use the subclass as the way to identify the type of error that
occurred (for example, InsufficientFundsException) and pass -1 as the error code.

Reading the Error Message from the string_db Table
If you are developing an application that needs to be localized, you may want to read your error
messages from the string_db table. In addition, error strings in the string_db table can contain
placeholders that you can replace when constructing your exception.

If you want to use a string in the string_db table as the error message, you need to add your
custom errors into the database before you can access them. For more information, see Adding Your
Error Code to the string_db Table on page 337.


                                                                                                      333
Handling Errors in Java




                  The following sample of Java code demonstrates how to create a subclass of the CboError class
                  that reads the error message from the string_db table. Note that some error strings in the
                  string_db table provide placeholders for string parameters to be passed in and formatted into
                  the error message. The sample below allows you to pass in an array of strings for those
                  placeholders.
                  import com.clarify.cbo.*;
                  ...
                  // Create a subclassed CboError object that accesses string_db.
                  public class CboJavaError extends CboError {
                     public CboJavaError (Session mySession, String source,
                       Object errorNumberOrName, String [] params) {
                         super(mySession, source, errorNumberOrName, params);
                     }
                  }

                  The following sample of Java code demonstrates how to throw an error that you subclassed from
                  the CboError object. Note that the CboError object uses the CBO Session object to access the
                  string_db table. For more information on the CBO Session object, see Overview: Using CBO in
                  an Application on page 268, and for details on working with the ClarifyCRM session object, see
                  Getting the Session Object on page 270.
                  // Set up and throw the error.
                  String strSource = "PurchaseMethod";
                  Integer nErrorNumber = new Integer(152043717);
                  String[] myParams = new String[1];
                  myParams[0] = "Credit Card Number";
                  if (disposable_income < total_cost) {
                     throw (new CboJavaError(clfySession, strSource, strErrorName,
                        myParams);
                  }
                  ...

                  The error number 152043717 corresponds to the following string in the string_db table:
                  %1 is required.

                  The following sample of Java code demonstrates how to catch the error class that you subclassed
                  from the CboError object.
                  // Catch the error
                  try {
                     ShoppingCart.checkout();
                     ...
                  } catch(CboJavaError e) {
                     // Get and print information about the error.
                     String strErrorMsg = "Error in " + e.getSource() + ": " +
                        e.getDescription();
                     System.out.println(strErrorMsg);
                  }
                  ...

                  In the example above, when the error is caught, it will generate the following message:
                      Error in PurchaseMethod: Credit Card Number is required.

                  For more information on passing arguments to your error messages, see Passing Arguments to the
                  Error Message on page 331. For more information on the CboError object and for information on
                  error strings in the string_db table, see the Core Business Objects Reference Guide.



334
                                                                              CHAPTER 10   Handling Errors




Specifying the Error Message in the Constructor
The following sample of Java code demonstrates how to create a subclass of the CboError error
object that does not access the string_db table.
import com.clarify.cbo.*;
...
// Create a subclassed CboError object for your Java application.
public class InsufficientFundsException extends CboError {
     public InsufficientFundsException (String source,
       int errorNumber, String errorMessage) {
         super(source, errorNumber, errorMessage);
     }
}

The next section of Java code shows an example of how to throw your own error that you
subclassed from the CboError object.
// Part of a method that checks a buyer’s purchasing power.
...
if (disposable_income < total_cost) {
     throw (new InsufficientFundsException("PurchaseMethod",
        -1, "Insufficient Funds!"));
}
...

The next section of Java code shows an example of how to catch errors in your Java application.
The code first tries to catch the specific error subclassed from the CboError object, then it tries to
catch errors thrown by CBO.
// Part of a method in which a buyer tries to make a purchase.
try {
      ...
      ShoppingCart.checkout();
} catch (InsufficientFundsException e) {
      // Get the error message and print it
      String strErrorMsg = "Error: " + e.getDescription() + " " +
         e.getSource();
      System.out.println(strErrorMsg);
} catch(Exception e) {
      // Get and print information about other exceptions.
      String strErrorMsg = "Error: " + e.getMessage();
      System.out.println(strErrorMsg);
   } finally {
      // Clean up the business objects.
      ...

In the example above, when the Java application error is caught, it will generate the following
message:
   Error: Insufficient Funds! PurchaseMethod




                                                                                                      335
Recovering from Database Update Failures




Recovering from Database Update Failures
                 When you call a method that commits changes to the database, some types of business objects set
                 up and modify data internally before committing the changes. If the database update fails, the data
                 in these business objects may still be in a modified, pre-update state.

                 To avoid problems, you may need to create a new business object of the same type and copy the
                 data from your existing business object to the new business object. You can then use the new
                 business object to attempt to commit your changes again.

                 The following example demonstrates what to do when a database update failure occurs.
                     In Java:
                     import com.clarify.cbo.*;
                     ...
                     try {
                        // Attempt to commit changes to the database.
                        BoFoo.method();
                     } catch(Exception e) {
                        // When a failure occurs, create a new business object of the same type.
                        Case BoFooNew = (Case) clfyBoContext.createBO("com.clarify.cbo.Case");
                        // Copy data from the existing business object to the new one.
                        BoFooNew.setValue("FieldName", BoFoo.getValue("FieldName"));
                        ...
                     }
                     ...
                     In JavaScript:
                     ...
                     // Make sure to use try...catch loops in JavaScript to catch
                     // any errors.
                     try {
                        // Attempt to commit changes to the database.
                        BoFoo.Method();
                     } catch(e) {
                        // When a failure occurs, create a new business object of the same type.
                        BoFooNew = ClfyForm.CreateBO("Clarify.CBO.Foo");
                        // Copy data from the existing business object to the new one.
                        BoFooNew("FieldName").Value = BoFoo("FieldName").Value;
                        ...
                     }
                     ...




336
                                                                                  CHAPTER 10   Handling Errors




Adding Your Error Code to the string_db Table
        In order to use a custom error code, you must create a row in the string_db table for the code and
        its corresponding message.

        Any custom error codes must be in the range of 25600 and 26099. Start numbering your error codes
        from 25600, and continue numbering your codes in sequence. (Do not leave any breaks in the
        sequence.)

        For example, the following dataex record specifies a custom error code and message for the
        English locale:
        OBJECT TYPE="string_db", NAME="string_25600" UNIQUE_FIELD=id , locale
           FIELDS
              id=25600;
              string="My custom error message.";
              name=CUSTOM_ERR;
              locale=0;
              type=0;
           END_FIELDS
           RELATIONS
           END_RELATIONS
        END_OBJECT NAME="string_25600"




                                                                                                          337
Adding Your Error Code to the string_db Table




338
C H A P T E R     1 1




Writing Event Handlers



CONTENTS
 Overview: Events and Business Objects 340

 Writing Event Listeners in Java 340

 Writing Event Handlers in Visual Basic 342

 Determining the Event Source 342

 Handling the FieldChanged Event 344

 Handling the RowEnter Event 345

 Handling the RowsetChanged Event 345

 Examples of Handling CBO Events 346




                                              339
Overview: Events and Business Objects




Overview: Events and Business Objects
                 CBOs fire the following events:
                     FieldChanged
                     A business object fires this event when the value of a field in a row has changed.
                     RowEnter
                     A business object fires this event when the position of the currently selected row has changed.
                     RowsetChanged
                     A business object fires this event when the rowset has changed (either because the rowset has
                     been reloaded or because rows have been added or removed from the rowset).

                 If you want to prevent a business object from firing any events, set the BO.DisableEvents
                 property to True.

                 If you are working in Visual Basic, you can write event handlers for the business objects. If you are
                 working in Java, you can write event listeners for these events and add these listeners to the
                 business objects.

                 For details on working with events in business objects, see the following sections:
                     Writing Event Listeners in Java on page 340
                     Writing Event Handlers in Visual Basic on page 342
                     Determining the Event Source on page 342
                     Handling the FieldChanged Event on page 344
                     Handling the RowEnter Event on page 345
                     Handling the RowsetChanged Event on page 345

                 For examples of handling events in CBO, see Examples of Handling CBO Events on page 346.




Writing Event Listeners in Java
                 In order to get notification of the business object events in Java, you need to define a class that
                 implements the com.clarify.cbo.CboEventListener interface. In the definition of this class,
                 you define fieldChanged, rowEnter, and rowsetChanged methods that the business object
                 calls when these events occur. (For details on these methods, see Handling the FieldChanged Event on
                 page 344, Handling the RowEnter Event on page 345, Handling the RowsetChanged Event on page 345.)

                 In your application code where you are creating the business objects, you create objects of your
                 newly defined class, and you use the BO.addCboEventListener method to register your object
                 as a listener to the business object. Make sure to cast your object as CboEventListener when you
                 pass in your object as an argument to this method.




340
                                                                      CHAPTER 11   Writing Event Handlers




For example, suppose that you define the following class that implements the
com.clarify.cbo.CboEventListener interface:
public class TestEventListener implements CboEventListener {
   ...
   public void rowEnter(Base bo) {
      ...
   }
   ...
   public void rowsetChanged(Base bo) {
      ...
   }
   public void fieldChanged(Base bo, String fieldName, int nRow) {
      ...
   }
}

In your application code, you use the following statements to register your object as a listener:
...
// Create a new Case business object.
boCase = (Case) clfyBoContext.createBO("com.clarify.cbo.Case");
boCase.setDataFields("*");
// Create an event listener.
TestEventListener evtCaseListener = new TestEventListener();
// Register your event listener.
boCase.addCboEventListener((CboEventListener)evtCaseListener);
// Query the business object.
boCase.query();
...

Unlike Visual Basic, events in contained business objects do not fire automatically. JavaBeans for
the contained business objects are not created until you access the contained objects (for example,
by calling the BO.getContainedBO method).

If you want to listen to the events in a contained business object (such as the Generic business
object for contact in the Case business object), you must get the contained business object using the
BO.getContainedBO method.

For example, the following section of code registers a listener with the Contact business object
and two contained business objects (accessed through the ContactRole and Site properties):
...
// Create an event listener.
TestContactEventListener evtContactListener =
   new TestContactEventListener();
// Register your event listener.
boContact.addCboEventListener((CboEventListener)evtContactListener);
// Set up the listener to report events for the contained BOs.
boContact.getContactRole();
boContact.getSite();
// Query the business object.
boContact.query();
...

To remove a listener from a business object, use the removeCboEventListener method. For
example, the following statement removes the listener evtCaseListener from boCase:
boCase.removeCboEventListener(evtCaseListener);



                                                                                                     341
Writing Event Handlers in Visual Basic




Writing Event Handlers in Visual Basic
                   You can write event handlers to check whether a field value is changed, whether the currently
                   selected row has changed, or whether the rowset has changed in some way.

                   All parameters are passed by value to the event handlers. When defining your event handlers,
                   make sure to use the ByVal keyword in Visual Basic. (Otherwise, Visual Basic will report a compile
                   error with the procedure declaration.)

                   If you programmatically created the business objects (instead of dropping the business objects on a
                   form), you can handle events by using the Visual Basic WithEvents keyword to declare a variable
                   of the business object type. For example:
                   Option Explicit
                   Private WithEvents BoContact As ClarifyCommonBusObj.Contact
                   ...
                   Sub BoContact_FieldChanged(ByVal pBO As Object, ByVal FieldName As String, ByVal
                   nRow As Long)
                   ...

                   If you are writing an event handler for a business object that contains other business objects (such
                   as the Case business object, which contains other business objects), all the events in the contained
                   business objects are bubbled up to the top-level business object. You must check the source of the
                   event to determine which business object fired the event. For details, see Determining the Event
                   Source on page 342.




Determining the Event Source
                   Certain types of business objects contain other business objects. For example, the Contact
                   business object contains a number of other business objects, including ContactRole and Site
                   business objects (which represent the contact roles and the corresponding sites related to each
                   contact). For an explanation of contained business objects, see Using Contained Business Objects on
                   page 216.

                   If a contained business object fires an event, that event is bubbled up to the top-level container
                   business object. For example, if a field changes in the Site business object contained in the
                   Contact business object, the event for that change bubbles up to the Contact business object. The
                   event causes the FieldChanged event procedure for the Contact business object to execute.

                   In order to determine which business object fired the event (the container business object or one of
                   its contained business objects), you can check the BO.InstanceId property of the business object
                   passed in to the event handler. The BO.InstanceId property uniquely identifies a business
                   object.

                   Note: Do not compare object references to find the business object that fired the event. In some situations, an
                   object reference may point to a proxy object, rather than to the underlying business object.




342
                                                                      CHAPTER 11   Writing Event Handlers




For example, the Contact business object contains the following business objects:
   ContactRole business object (representing the roles related to a contact)
   Site business object (representing the sites related to those roles)
   PhoneLog business object (representing the phone logs for that contact)

The contained PhoneLog business object also contains a Contact business object. Although both
of these business objects represent the same contacts, each business object fires its own events.
Events in the contained Contact business object are bubbled up to the containing Contact
business object.

The following event handler class in Java uses the BO.InstanceId property of the business object
to determine which business object fired the event. The constructor of the event handler class
passes in a reference to a Contact business object for use in the comparison.
import com.clarify.cbo.*;
// Class for reporting Contact BO events.
public class TestContactEventListener implements CboEventListener {
   private Contact m_boContact = null;
   private int m_ContactInstanceId = 0;
   private int m_ContactRoleInstanceId = 0;
   private int m_PhoneLogInstanceId = 0;
   private int m_PhoneLogContactInstanceId = 0;
   private int m_SiteInstanceId = 0;
   // The constructor passes in a Contact BO that is used to determine
   // which business object fired the event.
   TestContactEventListener(Contact boContact) {
      m_boContact = boContact;
      // Get the instance IDs of each contained business object to
      // determine which of these business objects fired the event.
      m_ContactInstanceId = m_boContact.getInstanceId();
      m_ContactRoleInstanceId =
         m_boContact.getContactRole().getInstanceId();
      m_PhoneLogInstanceId =
         m_boContact.getPhoneLog().getInstanceId();
      m_PhoneLogContactInstanceId =
         m_boContact.getPhoneLog().getContact().getInstanceId();
      m_SiteInstanceId = m_boContact.getSite().getInstanceId();
   }
   ...
   // Event handler for the FieldChanged event.
   public void fieldChanged(Base bo, String fieldName, int nRow) {
      // Get the instance ID of the business object that fired the event.
      int m_boInstanceId = bo.getInstanceId();
      // Compare the InstanceId property of the business object that
      // fired the event against the InstanceId properties of boContact
      // and its contained business objects to determine which one
      // fired the event.
      if (m_boInstanceId == m_ContactInstanceId) {
         System.out.println("EVENT: boContact FieldChanged");
      } else if (m_boInstanceId == m_ContactRoleInstanceId) {
         System.out.println("EVENT: boContact.ContactRole
            FieldChanged");
      } else if (m_boInstanceId == m_PhoneLogInstanceId) {
         System.out.println("EVENT: boContact.PhoneLog FieldChanged");
      } else if (m_boInstanceId == m_PhoneLogContactInstanceId) {



                                                                                                     343
Handling the FieldChanged Event




                            System.out.println("EVENT: boContact.PhoneLog.Contact
                               FieldChanged");
                         } else if (m_boInstanceId == m_SiteInstanceId) {
                            System.out.println("EVENT: boContact.Site FieldChanged");
                         } else {
                            System.out.println("Unknown BO");
                         }
                         ...

                 The following event handler in Visual Basic uses the BO.InstanceId property of the business
                 object to determine which business object fired the event. The subroutine assumes that you have
                 defined a global variable named BoContact that represents the Contact business object used in
                 the comparison. (See Writing Event Handlers in Visual Basic on page 342 for an example of the
                 declaration.)
                 Sub BoContact_FieldChanged(ByVal pBO As Object, ByVal FieldName As String, ByVal
                 nRow As Long)
                    'Compare the InstanceId property of the business object that fired the event
                    'against the InstanceId properties of BoContact and its contained business
                    'objects to determine which one fired the event.
                    Select Case pBO.InstanceId
                       Case Is = BoContact.InstanceId
                          Debug.Print "EVENT: BoContact FieldChanged"
                       Case Is = BoContact.ContactRole.InstanceId
                          Debug.Print "EVENT: BoContact.ContactRole FieldChanged"
                       Case Is = BoContact.PhoneLog.InstanceId
                          Debug.Print "EVENT: BoContact.PhoneLog FieldChanged"
                       Case Is = BoContact.PhoneLog.Contact.InstanceId
                          Debug.Print "EVENT: BoContact.PhoneLog.Contact FieldChanged"
                       Case Is = BoContact.Site.InstanceId
                          Debug.Print "EVENT: BoContact.Site FieldChanged"
                       Case Else
                          Debug.Print "EVENT: Unknown BO FieldChanged"
                    End Select
                    ...




Handling the FieldChanged Event
                 A business object fires a FieldChanged event when you change a value in a row in that object.
                 Certain operations may change the value of a field automatically, which causes the business object
                 to fire this event. For example, suppose that you add a new row by calling the BO.AddNew method.
                 When you call the BO.Update method to commit the new row, the method sets the new object ID
                 in the row. This changes the value of the objid field in the row and fires a FieldChanged event.

                 When a FieldChanged event handler is executed, the following parameters are passed:
                     A reference to the business object that fired the event (you can use this reference to determine
                     the source of the event, as explained in Determining the Event Source on page 342)
                     The name of the field that has been changed
                     The position of the row where the field value changed

                 For examples of handling this type of event, see Java Example of a CBO Event Listener on page 346
                 and Visual Basic Example of CBO Event Handlers on page 350.


344
                                                                                 CHAPTER 11   Writing Event Handlers




Handling the RowEnter Event
        A business object fires a RowEnter event when you change the position of the currently selected
        row. The position can change for a number of different reasons:
           You explicitly change the position by calling one of the BO.MoveXXX methods (such as
           BO.MoveFirst and BO.MoveNext) or by setting the BO.Position property.
           You add a new row (for example, by calling the BO.AddNew, BO.Add, or BO.Duplicate
           methods), which changes the position of the currently selected row to the new row.
           You remove or delete the currently selected row (for example, by calling the BO.Remove or
           BO.Delete methods), which changes the position of the currently selected row to the next row.
           You load the rowset by querying the database (for example, by calling the BO.Query method)
           or, if the business object is the child of another business object, by selecting a different row in the
           parent business object.

        When a RowEnter event handler is executed, a reference to the business object that fired the event
        is passed an argument. You can use this reference to determine the source of the event (see
        Determining the Event Source on page 342 for details).

        For examples of handling this type of event, see Java Example of a CBO Event Listener on page 346
        and Visual Basic Example of CBO Event Handlers on page 350.




Handling the RowsetChanged Event
        A business object fires a RowsetChanged event when you change the rowset in the object. The
        rowset can change for a number of different reasons:
           You add, remove, or delete a row from the rowset (for example, by calling the BO.AddNew,
           BO.Add, BO.Duplicate, BO.Remove, or BO.Delete methods).
           You load the rowset by querying the database (for example, by calling the BO.Query method).
           You load the rowset of a child business object (or a contained business object) by selecting a
           different row in the parent business object.

        When a RowsetChanged event handler is executed, a reference to the business object that fired the
        event is passed an argument. You can use this reference to determine the source of the event (see
        Determining the Event Source on page 342 for details).

        For examples of handling this type of event, see Java Example of a CBO Event Listener on page 346
        and Visual Basic Example of CBO Event Handlers on page 350.




                                                                                                                345
Examples of Handling CBO Events




Examples of Handling CBO Events
                 The following sections contain examples of handling CBO events:
                    Java Example of a CBO Event Listener on page 346
                    Visual Basic Example of CBO Event Handlers on page 350


                 Java Example of a CBO Event Listener
                 The following example defines two Java classes:
                    TestContactEventListener (a CBO event listener class)
                    TestEvents (a class that registers TestContactEventListener as an event listener for a
                    Contact business object)

                 The TestContactEventListener class implements the CboEventListener interface. This
                 class handles the RowEnter, RowsetChanged, and FieldChanged events for the Contact
                 business object and its contained ContactRole and Site business objects. (Events from other
                 contained business objects are ignored.) When any of these events occur, the class prints
                 information about the event to standard output.
                 import com.clarify.cbo.*;

                 public class TestContactEventListener implements CboEventListener {
                    private Contact m_boContact = null;
                    // Instance IDs used to determine which business object fired
                    // the event.
                    private int m_ContactInstanceId = 0;
                    private int m_ContactRoleInstanceId = 0;
                    private int m_SiteInstanceId = 0;

                    // The constructor passes in a Contact business object and uses this
                    // object to get its instance ID as well as the instance IDs of the
                    // contained ContactRole and Site business objects. The methods
                    // use these instance IDs to determine which object fired the event
                    // (the Contact business object or the contained ContactRole or Site
                    // business objects).
                    TestContactEventListener(Contact boContact) {
                       m_boContact = boContact;
                       m_ContactInstanceId = m_boContact.getInstanceId();
                       m_ContactRoleInstanceId =
                          m_boContact.getContactRole().getInstanceId();
                       m_SiteInstanceId = m_boContact.getSite().getInstanceId();
                    }

                    // This method handles the RowEnter event. When a RowEnter event
                    // occurs, the method prints out the position of the currently
                    // selected row and the object ID of that row.
                    public void rowEnter(Base bo) {
                       System.out.print("EVENT: ");
                       // Determine which business object fired the event.
                       int m_boInstanceId = bo.getInstanceId();
                       if (m_boInstanceId == m_ContactInstanceId) {
                          // The Contact business object fired the event.
                          System.out.print("boContact");


346
                                                       CHAPTER 11   Writing Event Handlers




    } else if (m_boInstanceId == m_ContactRoleInstanceId) {
       // The contained ContactRole business object fired the event.
       System.out.print("boContact.ContactRole");
    } else if (m_boInstanceId == m_SiteInstanceId) {
       // The contained Site business object fired the event.
       System.out.print("boContact.Site");
    } else {
       // Ignore events fired by other contained business objects.
       return;
    }
    // Print information about the event.
    System.out.println(" RowEnter");
    System.out.println("Position: " + bo.getPosition() +
       " Object ID: " + bo.getId());
}

// This method handles the RowsetChanged event. When a RowsetChanged
// event occurs, the method prints out the position of the currently
// selected row, the object ID of that row, and the count and
// total count of the rows in the rowset.
public void rowsetChanged(Base bo) {
   System.out.print("EVENT: ");
   // Determine which business object fired the event.
   int m_boInstanceId = bo.getInstanceId();
   if (m_boInstanceId == m_ContactInstanceId) {
      // The Contact business object fired the event.
      System.out.print("boContact");
   } else if (m_boInstanceId == m_ContactRoleInstanceId) {
      // The contained ContactRole business object fired the event.
      System.out.print("boContact.ContactRole");
   } else if (m_boInstanceId == m_SiteInstanceId) {
      // The contained Site business object fired the event.
      System.out.print("boContact.Site");
   } else {
      // Ignore events fired by other contained business objects.
      return;
   }
   // Print information about the event.
   System.out.println(" RowsetChanged");
   System.out.println("Position: " + bo.getPosition() +
      " Object ID: " + bo.getId());
   System.out.println("Count: " + bo.getCount());
   System.out.println("Total Count: " + bo.getTotalCount());
}

// This method handles the FieldChanged event. When a FieldChanged
// event occurs, the method prints out the position of the row
// where the change occurred, the object ID of that row, the name
// of the field that changed, and the new value of the field.
public void fieldChanged(Base bo, String fieldName, int nRow) {
   System.out.print("EVENT: ");
   // Determine which business object fired the event.
   int m_boInstanceId = bo.getInstanceId();
   if (m_boInstanceId == m_ContactInstanceId) {
      // The Contact business object fired the event.
      System.out.print("boContact");
   } else if (m_boInstanceId == m_ContactRoleInstanceId) {


                                                                                      347
Examples of Handling CBO Events




                            // The contained ContactRole business object fired the event.
                            System.out.print("boContact.ContactRole");
                         } else if (m_boInstanceId == m_SiteInstanceId) {
                            // The contained Site business object fired the event.
                            System.out.print("boContact.Site");
                         } else {
                            // Ignore events fired by other contained business objects.
                            return;
                         }
                         // Print information about the event.
                         System.out.println(" FieldChanged");
                         System.out.println("Position: " + nRow + " Object ID: "
                            + bo.getId());
                         System.out.println("Changed: " + fieldName + " = "
                            + bo.getValue(fieldName));
                     }
                 }

                 The main method of the TestEvents class creates a Contact business object, creates a
                 TestContactEventListener object, and registers this object as an event listener for the
                 Contact business object. The method then queries the Contact business object and iterates
                 through each row in the rowset.
                 import com.clarify.cbo.*;

                 // This class contains a main method that demonstrates how to handle
                 // events in CBO.
                 public class TestEvents {

                     public static void main(String[] args) {

                         Application clfyApp = null;
                         Session clfySession = null;
                         BoContext clfyBoContext = null;
                         Contact boContact = null;
                         String strLogin = "clfyuser";
                         String strPassword = "password";
                         String strFieldName = "e_mail";
                         String strNewFieldValue = "jdoe@site.com";

                         try {

                           // Create the Application object.
                           clfyApp = new com.clarify.cbo.Application();
                           // Get the global Session object.
                           clfySession = clfyApp.getGlobalSession();
                           // Log in to the database.
                           clfySession.login(strLogin, strPassword);
                           // Create a new BoContext object for the current session.
                           clfyBoContext = clfySession.createBoContext();

                           // Create a new Contact business object.
                           boContact =
                              (Contact) clfyBoContext.createBO("com.clarify.cbo.Contact");
                           boContact.setDataFields("*");

                           // Create an event listener for the Contact business object.



348
                                                           CHAPTER 11   Writing Event Handlers




          // Pass the Contact business object to the constructor so that
          // the event handling methods can use its instance IDs to
          // determine which business object fired the event.
          TestContactEventListener evtContactListener =
             new TestContactEventListener(boContact);
          // Register the event listener to the Contact business object.
          boContact.addCboEventListener(
             (CboEventListener)evtContactListener);

          // Query the database.
          System.out.println("==> Querying <==");
          boContact.query();
          System.out.println("==> Done querying <==\n");

          // Iterate through each row in the rowset.
          while (! boContact.getEOF()) {
             System.out.println("==> Moving to next row <==");
             boContact.moveNext();
             if (! boContact.getEOF()) {
                System.out.println("==> Done moving <==\n");
             } else {
                System.out.println("==> Already at end of rowset <==\n");
             }
          }

          // Change the value of a field.
          System.out.println("==> Setting " + strFieldName + " to " +
             strNewFieldValue + "<==");
          boContact.setValue(strFieldName, strNewFieldValue);
          System.out.println("==> Done setting field value <==\n");

          // Remove the event listener.
          boContact.removeCboEventListener(evtContactListener);
          System.out.println("Removed Contact listener.");

        } catch(CboError e) {
           // Print out any error information.
           System.out.println(e.getCompleteInfo());
        } catch(Exception e) {
           // Print out any error information.
           e.printStackTrace();
        } finally {
           if (clfySession != null) {
              // End the current session and release the BoContext object
              // and any associated business objects.
              clfySession.release();
           }
           if (clfyApp != null) {
              // Shut down the application.
              clfyApp.shutdown();
              // Release the Application object.
              clfyApp.release();
           }
        }
    }
}



                                                                                          349
Examples of Handling CBO Events




                 Visual Basic Example of CBO Event Handlers
                 The following example defines event handlers for the RowEnter, RowsetChanged, and
                 FieldChanged events for a Contact business object. When any of these events occur, the event
                 handlers print information about the event to standard output, if the event is caused by the
                 Contact business object or the contained ContactRole or Site business object. (The event
                 handlers ignore events from all other contained business objects.)
                 ...
                 Option Explicit
                 Private WithEvents BoContact As ClarifyCommonBusObj.Contact
                 Private ClfyApp
                 Private ClfySession As ClarifyCommonBusObj.Session
                 Private ClfyForm As ClarifyCommonBusObj.FormContext
                 ...
                 Private Sub Form_Load()
                    'Set up the CBO environment and log in.
                    Set ClfyApp = CreateObject("Clarify.CBO.App.1")
                    Set ClfySession = ClfyApp.GlobalSession
                    ClfySession.Login "clfyuser", "password"
                    Set ClfyForm = ClfySession.CreateFormContext

                    'Create a Contact business object.
                    Set BoContact = ClfyForm.CreateBO("Clarify.CBO.Contact")
                    BoContact.DataFields = "*"

                    'When you query the database, RowsetChanged and RowEnter events
                    'are fired for BoContact and its contained business objects.
                    'The event handler for BoContact handles all of these events.
                    Debug.Print "Querying BoContact..."
                    BoContact.Query

                    'When you move to the next row, RowEnter events are fired for
                    'BoContact and its contained business objects. RowsetChanged
                    'events are also fired for all contained business objects.
                    'The event handler for BoContact handles all of these events.
                    Debug.Print "Moving to next row in BoContact..."
                    BoContact.MoveNext

                    'When you change the value of a field, a FieldChanged event
                    'is fired for the business object containing that field.
                    Debug.Print "Changing the fax_number field in BoContact..."
                    BoContact("fax_number").Value = "800 555-1212"
                    Debug.Print "Changing the region field in BoContact.Site..."
                    BoContact.Site("region") = "Western Region"

                    Unload Me
                 End Sub

                 Sub BoContact_RowsetChanged(ByVal pBO As Object)
                    'Determine which business object fired the event.
                    Select Case pBO.InstanceId
                       Case Is = BoContact.InstanceId
                          'The Contact business object fired the event.
                          Debug.Print "EVENT: BoContact RowsetChanged"
                       Case Is = BoContact.ContactRole.InstanceId
                          'The contained ContactRole business object fired the event.


350
                                                         CHAPTER 11   Writing Event Handlers




         Debug.Print "EVENT: BoContact.ContactRole RowsetChanged"