2 Working with APIs

This chapter contains the following topics:

2.1 Understanding APIs

This section discusses:

  • API fundamentals

  • Common library APIs

  • Database APIs

2.1.1 API Fundamentals

APIs are routines that perform predefined tasks. JD Edwards EnterpriseOne APIs make it easier for third-party applications to interact with JD Edwards EnterpriseOne software. These APIs are functions that you can use to manipulate JD Edwards EnterpriseOne data types, provide common functionality, and access the database. Several categories of APIs exist, including the Common Library Routines and JD Edwards EnterpriseOne Database (JDEBASE) APIs.

Programing with APIs is useful for these reasons:

  • No code modifications are required as functionality is upgraded.

  • When a data structure changes, source modifications are minimal to nonexistent.

  • Common functionality is provided through the APIs, and they are less prone to error.

When the code in an API changes, business functions typically only need to be recompiled and relinked.

2.1.2 Common Library APIs

The Common Library APIs, such as determining whether foreign currency is enabled, manipulating the date format, retrieving link list information, or retrieving math numeric and date information are specific to JD Edwards EnterpriseOne functionality. You can use these APIs to set up data by calling APIs and modifying data after API calls. Some of the more commonly used categories of APIs include MATH_NUMERIC, JDEDATE, and LINKLIST. Other miscellaneous Common Library APIs are also available.

JD Edwards EnterpriseOne provides the data types, MATH_NUMERIC and JDEDATE, for use when creating business functions. Because these data types might change, you must use the Common Library APIs provided by JD Edwards EnterpriseOne to manipulate the variables of these data types.

2.1.2.1 MATH_NUMERIC Data Type

The MATH_NUMERIC data type exclusively represents all numeric values in JD Edwards EnterpriseOne software. The values of all numeric fields on a form or batch process are communicated to business functions in the form of pointers to MATH_NUMERIC data structures. MATH_NUMERIC is used as a data dictionary (DD) data type.

The data type is defined as follows:

struct tagMATH_NUMERIC
{
 ZCHAR String[MAXLEN_MATH_NUMERIC+1];/* Just the digits - no separators */
 BYTE Sign;    /* �-� if negative, 0x00 otherwise */
 ZCHAR EditCode;   /* The Data Dictionary edit code to Format for display */
 short nDecimalPosition;  /* # of digits from right end of string to decimal point⇒
 */
 short nLength;   /* The number of digits in s */
 WORD wFlags;   /* Processing Flags */
 ZCHAR szCurrency[CURRENCY_CODE_SIZE];/* The Currency Code */
 short nCurrencyDecimals;  /* The Number of Currency Decimals */
 short nPrecision;   /* The Data Dictionary Size */
};

This table lists various elements:

MATH_NUMERIC Element Description
String Digits without separators
Sign A minus sign indicates the number is negative, otherwise the value is 0x00
EditCode Data dictionary edit code that formats the number for display
nDecimalPosition Number of digits from the right to place the decimal
nLength Number of digits in the string
wFlags Processing flags
szCurrency Currency code
nCurrencyDecimals Number of currency decimals
nPrecision Data dictionary size

2.1.2.2 JDEDATE Data Type

The JDEDATE data type exclusively represents all dates in JD Edwards EnterpriseOne software. The values of all date fields on a form or batch process are communicated to business functions in the form of pointers to JDEDATE data structures. JDEDATE is used as a data dictionary data type.

This code sample illustrates defining the data type:

struct tagJDEDATE
{
 short nYear;;
 short nMonth;;
 short nDay;
};
typedef struct tagJDEDATE JDEDATE, FAR *LPJDEDATE;

This table lists the elements in the JDEDATE data type:

JDEDATE Element Description
nYear Year (4 digits)
nMonth Month
nDay Day

2.1.3 Database APIs

JD Edwards EnterpriseOne software supports multiple databases. An application can access data from a number of databases.

2.1.3.1 Standards and Portability

These standards affect the development of relational databases:

  • ANSI (American National Standards Institute) standard.

  • X/OPEN (European body) standard.

  • ISO (International Standards Institute) SQL standard.

Ideally, industry standards enable users to work identically with different relational database systems. Although each major vendor supports industry standards, it also offers extensions to enhance the functionality of the SQL language. Vendors also periodically release upgrades and new versions of their products.

These extensions and upgrades affect portability. Due to the industry impact of software development, applications need a standard interface to databases that is not affected by differences between database vendors. When a vendor provides a new release, the affect on existing applications should be minimal. To solve many of these portability issues, many organizations use standard database interfaces called open database connectivity (ODBC).

2.1.3.2 JD Edwards EnterpriseOne ODBC

JD Edwards EnterpriseOne ODBC enables you to use one set of functions to access multiple relational database management systems. Consequently, you can develop and compile applications knowing that they can run on a variety of database types with the correct database driver. Database drivers are installed that enable the JD Edwards EnterpriseOne ODBC interface to communicate with a specific database system using a database driver.

The driver handles the I/O buffers to the database, which enables a programmer to write an application that communicates with a generic data source. The database driver is responsible for processing the API request and communicating with the correct data source. The application does not have to be recompiled to work with other databases. If the application must perform the same operation with another database, a new driver is loaded.

A driver manager handles all application requests to the JD Edwards EnterpriseOne database function call. The driver manager processes the request or passes it to an appropriate driver.

JD Edwards EnterpriseOne applications access data from heterogeneous databases, using the JDB API to interface between the applications and multiple databases. Applications and business functions use the JDB API to dynamically generate platform-specific SQL statements. JDB also supports additional features, such as replication and cross-data source joins.

2.1.3.3 Standard JDEBASE API Categories

You can use control and request level APIs to develop and test business functions. This table lists the categories of JDEBASE APIs:

Category Description
Control Level Provides functions for initializing and terminating the database connection.
Request Level Provides functions for performing database transactions. The request level functions perform these tasks:
  • Connect to and disconnect from tables and business views in the database.

  • Perform data manipulation operations of select, insert, update, and delete.

  • Retrieve data with fetch commands.

Column Level Performs and modifies information for columns and tables.
Global Table/Column Specifications Provides the capability to create and manipulate column specifications.

2.1.3.4 Connecting to a Database

To perform a request, the driver manager and driver must manage the information for the development environment, each application connection, and the SQL statement. The pointers that return this information to the application are called handles. The APIs must include these handles in each function call. Handles used by the development environment include these handles:

Handle Purpose
HENV The environment handle contains information related to the current database connection and valid connection handles. Every application connecting to the database must have an environment handle. This handle is required to connect to a data source.
HUSER The user handle contains information related to a specific connection. Each user handle has an associated environment handle with it. A connection handle is required to connect to a data source. If you are using transaction processing, initializing HUSER indicates the beginning of a transaction.
HREQUEST The request handle contains information related to a specific request to a data source. An application must have a request handle before executing SQL statements. Each request handle is associated with a user handle.

2.1.3.5 Understanding Database Communication Steps

Several APIs called in succession can perform these steps for database communication:

  • Initialize communication with the database.

  • Establish a connection to the specific data to access.

  • Execute statements on the database.

  • Release the connection to the database.

  • Terminate communication with the database.

This table lists some of the API levels and the communication handles and API names that are associated with them:

API Level Communication Handles API Name
Control level (application or test driver) Environment handle JDB_InitEnv
Control level (application or test driver) User handle (created) JDB_InitUser
Request level (business function) User handle (retrieved) JDB_InitBhvr
Request level (business function) Request handle JDB_OpenTable
Request level (business function) Request handle JDB_FetchKeyed()
Request level (business function) Request handle JDB_CloseTable
Request level (business function) User handle JDB_FreeBhvr
Control level (application or test driver) User handle JDB_FreeUser
Control level (application or test driver) Environment handle JDB_FreeEnv

2.2 Calling APIs

This section discusses how to:

  • Call an API from an external business function.

  • Call a Visual Basic program from JD Edwards EnterpriseOne software.

2.2.1 Calling an API from an External Business Function

You can call APIs from external business functions. To call an API from an external business function, you must first determine the function-calling convention of the .dll that you are going to use. It can be either cdecl or stdcall. The code might change slightly depending on the calling convention. This information should be included in the documentation for the .dll. If you do not know the calling convention of the .dll, you can execute the dumpbin command to determine the calling convention. Execute this command from the MSDOS prompt window:

 dumpbin /EXPORTS ExternalDll.DLL. 

Dumpbin displays information about the dll. If the output contains function names preceded by _ and followed by an @ sign with additional digits, the dll uses the stdcall calling convention; otherwise, it uses cdecl.

2.2.1.1 Stdcall Calling Convention

This example is standard code for Windows programs and is not specific to JD Edwards EnterpriseOne software:

# ifdef JDENV_PC
HINSTANCE hLibrary = LoadLibrary(_TEXT(YOUR_LIBRARY.DLL)); // substitute the name⇒
 of the external dll
if(hLibrary)
{
// create a typedef for the function pointer based on the parameters and return⇒
 type of the function to be called. This information can be obtained
// from the header file of the external dll. The name of the function to be called⇒
 in the following code is StartInstallEngine. We create a typedef for
// a function pointer named PFNSTARTINSTALLENGINE. Its return type is BOOL. Its⇒
 parameters are HUSER, LPCTSTR, LPCTSTR, LPTSTR & LPTSTR.
// Substitute these with parameter and return types for the particular API.
typedef BOOL (*PFNSTARTINSTALLENGINE) (HUSER, LPCTSTR, LPCTSTR, LPTSTR, LPTSTR);
// Now create a variable for the function pointer of the type you just created.⇒
 Then make call to GetProcAddress function with the first
// parameter as the handle to the library you just loaded. The second parameter⇒
 should be the name of the function you want to call prepended
// with an _, and appended with an @ followed by the total number of bytes for the⇒
 parameters. In this example, the total number of bytes in the
// parameters for StartInstallEngine is 20 ( 4 bytes for each parameter ). The Get⇒
ProcAddress API will return a pointer to the function that you need to
// call.
PFNSTARTINSTALLENGINE lpfnStartInstallEngine = (PFNSTARTINSTALLENGINE) GetProc⇒
Address(hLibrary, _StartInstallEngine@20);
if ( lpfnStartInstallEngine )
{
// Now call the API by passing in the requisite parameters.
lpfnStartInstallEngine(hUser, szObjectName, szVersionName, pszObjectText, szObject⇒
Type);
}
#endif

2.2.1.2 Cdecl Calling Convention

The process for using the cdecl calling convention is similar to the process for using the std calling convention. They differ principally in the second parameter for GetProcAddress. Note the comments that precede that call.

# ifdef JDENV_PC
HINSTANCE hLibrary = LoadLibrary(_TEXT(YOUR_LIBRARY.DLL)); // substitute the name⇒
 of the external dll
if(hLibrary)
{
// create a typedef for the function pointer based on the parameters and return⇒
 type of the function to be called. This information can be obtained
// from the header file of the external dll. The name of the function to be called⇒
 in the following code is StartInstallEngine. We create a typedef for
// a function pointer named PFNSTARTINSTALLENGINE. Its return type is BOOL. Its⇒
 parameters are HUSER, LPCTSTR, LPCTSTR, LPTSTR & LPTSTR.
// Substitute these with parameter and return types for the particular API.
typedef BOOL (*PFNSTARTINSTALLENGINE) (HUSER, LPCTSTR, LPCTSTR, LPTSTR, LPTSTR);
// Now create a variable for the function pointer of the type you just created.⇒
 Then make call to GetProcAddress function with the first
// parameter as the handle to the library you just loaded. The second parameter⇒
 should be the name of the function you want to call. In this
// case it will be StartInstallEngine only. The GetProcAddress API will return a⇒
 pointer to the function that you need to call.
PFNSTARTINSTALLENGINE lpfnStartInstallEngine = (PFNSTARTINSTALLENGINE) GetProc⇒
Address(hLibrary, StartInstallEngine);
if ( lpfnStartInstallEngine )
{
// Now call the API by passing in the requisite parameters.
lpfnStartInstallEngine(hUser, szObjectName, szVersionName, pszObjectText, szObject⇒
Type);
}
#endif

Note:

These calls work only on a Windows client machine. LoadLibrary and GetProcAddress are Windows APIs. If the business function is compiled on a server, the compile will fail.

2.2.2 Calling a Visual Basic Program from JD Edwards EnterpriseOne Software

You can call a Visual Basic program from a JD Edwards EnterpriseOne business function and pass a parameter from the Visual Basic program to the JD Edwards EnterpriseOne business function using this process:

  1. Write the Visual Basic program into a Visual Basic .dll that exports the function name of the program and returns a parameter to the JD Edwards EnterpriseOne business function.

  2. Write a business function that loads the Visual Basic .dll using the win32 function LoadLibrary.

  3. In the business function that you create, call the win32 function GetProcAddress to get the Visual Basic function and call it.

2.3 Using the SAX Parser

This section provides an overview of the SAX parser and of examples for its use.

2.3.1 Understanding the SAX Parser

The SAX parser is one of two main parsers used for XML data. It is an events-based parser, as opposed to the other XML parser, DOM, which is a tree-based parser. The Xerces product, from the Apache organization, provides both XML parsers. The Xerces code is written in C++. To make XML parsing available to business functions, a C-API interface, XercesWrapper, exists to provide access to both parsers. The design of the parsers is quite different, and that provides advantages for each parser, depending on the intended usage.

The DOM parser reads the XML file and builds an internal model (DOM document tree) of that file in memory. This has the advantage of enabling you to traverse the tree, retrieve parent-child relationships, and revisit the same data multiple times. The disadvantages include high memory requirements for large XML files. Also, the entire XML file must be read into memory before any of the data in the DOM document tree can begin to be processed. The DOM parser can also be used to programmatically build a DOM document tree in memory, and then write that tree to a file, in XML format.

The SAX parser reads an XML file and as each item is read, the parser passes that piece of data to callback functions. This methodology has the advantage of enabling fast processing with minimal memory usage. Also, the parsing can be stopped after a specific item has been found. The disadvantages include that the current state of parsing must be maintained by the callback functions, and previous data items can not be revisited without rereading the XML file. Finally, the SAX parser is a read-only parser.

This is a typical sequence used for parsing an XML data file using the DOM parser:

  1. Initialize the XercesWrapper, which in turn, initializes the Xerces code.

  2. Initialize the DOM parser.

  3. Parse the XML data file.

  4. Retrieve a pointer to the root element of the DOM document tree.

  5. Retrieve additional elements and data, by traversing the DOM document tree.

    The callback functions are called whenever the specified events in the XML file are parsed.

  6. Free all DOM elements that have been retrieved.

  7. Free the DOM document tree.

  8. Free the DOM parser.

  9. Terminate the XercesWrapper interface, which in turn, closes the Xerces code.

This is a typical sequence used for parsing an XML data file, using the SAX parser:

  1. Initialize the XercesWrapper, which in turn, initializes the Xerces code.

  2. Initialize the SAX parser.

  3. Set up various callback functions for specific parsing events.

  4. Parse the XML data file.

  5. Call the callback functions as each event in the XML file is parsed.

  6. Within the callback functions, process the retrieved data and maintain a context for coordination between callback functions.

  7. Free the SAX parser.

  8. Terminate the XercesWrapper interface, which in turn, closes the Xerces code.

2.3.2 Examples of SAX Parser Usage

Many of the initialization, parsing, and termination functions are the same for both SAX and DOM parsers. The major difference is that the DOM parser returns a document handle which is then used with the traversing and data retrieval functions. Those functions are not used with SAX. SAX does all of the data processing within the user-defined callback functions. The callback functions are not used with DOM.

The processing of SAX-parsed data items occurs within the callback functions. Typically, each callback function maintains a context. The context can be passed to all callback functions and can be implemented as a data structure. The context, plus the other data passed to the callback functions, enables each data item to be processed appropriately.

2.3.2.1 Example Context Data Structure

This is a sample function which uses the SAX parser:

typedef struct tagParserCallbackValues {
 FILE *fp;
 JCHAR *szIndentString;
 int nIndentLevel;
} ZCALLBACK_VALUES, *PCALLBACK_VALUES;

2.3.2.2 Example Main Function

This is a sample context data structure:

/* SAX callbacks - display callback events into file */
int testcase_read_15(JCHAR *m_infile, JCHAR *m_outfile)
{
 XRCS_Status XRCSStatus;
 XRCS_hParser hParser;
 ZCALLBACK_VALUES zCbValues;
 PCALLBACK_VALUES pCbValues = &zCbValues;

 /* initialize context structure */
 pCbValues->fp = NULL;
 pCbValues->szIndentString = _J(" ");
 pCbValues->nIndentLevel = 0;

 /* open display file */
 pCbValues->fp = jdeFopen(m_outfile, _J("w"));

 if (pCbValues->fp != NULL)
 {
  XRCSStatus = XRCS_initEngine();
  if(XRCSStatus != XRCS_SUCCESS) {
   return -1;
  }

  XRCSStatus = XRCS_getParserByType(&hParser, XRCS_SAX_PARSER_TYPE);
  if(XRCSStatus != XRCS_SUCCESS) {
   return -1;
  }

  XRCSStatus = XRCS_setCallback(hParser, XRCS_CALLBACK_START_DOC,
   (void *) cb_startDoc_Display, (void *) pCbValues);
  if(XRCSStatus != XRCS_SUCCESS) {
   return -1;
  }

  /* set up callbacks for the SAX parser */
  XRCSStatus = XRCS_setCallback(hParser, XRCS_CALLBACK_END_DOC,
   (void *) cb_endDoc_Display, (void *) pCbValues);
  if(XRCSStatus != XRCS_SUCCESS) {
   return -1;
  }

  XRCSStatus = XRCS_setCallback(hParser, XRCS_CALLBACK_START_ELEM,
   (void *) cb_startElement_Display, (void *) pCbValues);
  if(XRCSStatus != XRCS_SUCCESS) {
   return -1;
  }

  XRCSStatus = XRCS_setCallback(hParser, XRCS_CALLBACK_END_ELEM,
   (void *) cb_endElement_Display, (void *) pCbValues);
  if(XRCSStatus != XRCS_SUCCESS) {
   return -1;
  }

  XRCSStatus = XRCS_setCallback(hParser, XRCS_CALLBACK_CHARACTERS,
   (void *) cb_characters_Display, (void *) pCbValues);
  if(XRCSStatus != XRCS_SUCCESS) {
   return -1;
  }

  XRCSStatus = XRCS_setCallback(hParser,
   XRCS_CALLBACK_IGNORABLE_WHITESPACE,
   (void *) cb_ignorableWhitespace_Display, (void *) pCbValues);
  if(XRCSStatus != XRCS_SUCCESS) {
   return -1;
  }

  XRCSStatus = XRCS_setCallback(hParser, XRCS_CALLBACK_FATAL_ERROR,
   (void *) cb_fatalError_Display, (void *) pCbValues);
  if(XRCSStatus != XRCS_SUCCESS) {
   return -1;
  }

  XRCSStatus = XRCS_setCallback(hParser, XRCS_CALLBACK_ERROR,
   (void *) cb_error_Display, (void *) pCbValues);
  if(XRCSStatus != XRCS_SUCCESS) {
   return -1;
  }

  XRCSStatus = XRCS_setCallback(hParser, XRCS_CALLBACK_WARNING,
   (void *) cb_warning_Display, (void *) pCbValues);
  if(XRCSStatus != XRCS_SUCCESS) {
   return -1;
  }

  /* now do the actual parsing */
  XRCSStatus = XRCS_parseXMLFile(hParser,m_infile, NULL);
  if(XRCSStatus != XRCS_SUCCESS) {
   return -1;
  }

  XRCSStatus = XRCS_freeParser(hParser);
  XRCSStatus = XRCS_terminateEngine();

  /* close display file */
  jdeFclose(pCbValues->fp);
 }
 else
 {
  /* could not open display file */
  return -1; }

 return 0;
}

2.3.2.3 Example Callback Functions

These are sample callback functions:

/* callbacks for display of SAX parser events */
XRCS_CallbackStatus cb_startDoc_Display(void *pContext)
{
 PCALLBACK_VALUES pCbValues = (PCALLBACK_VALUES) pContext;

 indentNewLine(pCbValues);
 jdeFprintf(pCbValues->fp, _J("START DOCUMENT"));
 return( XRCS_CB_CONTINUE);
}

XRCS_CallbackStatus cb_endDoc_Display(void *pContext)
{
 PCALLBACK_VALUES pCbValues = (PCALLBACK_VALUES) pContext;

 indentNewLine(pCbValues);
 jdeFprintf(pCbValues->fp, _J("END DOCUMENT"));
 indentNewLine(pCbValues);
 return( XRCS_CB_CONTINUE);
}

XRCS_CallbackStatus cb_startElement_Display(void *pContext,
 const JCHAR *szUri,
 const JCHAR *szLocalname,
 const JCHAR *szQname,
 unsigned int nNumAttrs,
 const XRCS_ATTR_INFO *pAttributes)
{
 PCALLBACK_VALUES pCbValues = (PCALLBACK_VALUES) pContext;
 unsigned int nAttrNum;
 const XRCS_ATTR_INFO * thisAttr = NULL;

 pCbValues->nIndentLevel++;
 /* display element name */
 indentNewLine(pCbValues);
 jdeFprintf(pCbValues->fp, _J("ELEMENT: "));
 if (jdeStrlen( szLocalname) != 0)
 {
  jdeFprintf(pCbValues->fp, _J("<%ls"), szLocalname);
 }
 else
 {
  jdeFprintf(pCbValues->fp, _J("<%ls"), szQname);
 }
 /* display attributes */
 if (nNumAttrs > 0U)
 {
  for (nAttrNum = 0U; nAttrNum < nNumAttrs; nAttrNum++)
  {
   thisAttr = &pAttributes[nAttrNum];
   /* display attrribute name */
   indentNewLine(pCbValues);
   jdeFprintf(pCbValues->fp, _J(" ATTR: "));
   if (jdeStrlen( thisAttr->szAttrLocalname) != 0)
   {
   jdeFprintf(pCbValues->fp, _J("%ls"),
    thisAttr->szAttrLocalname);
   }
   else
   {
   jdeFprintf(pCbValues->fp, _J("%ls"), thisAttr->szAttrQname);
   }
   /* display attribute value */
   jdeFprintf(pCbValues->fp, _J(" \""));
   jdeFprintf(pCbValues->fp, _J("%ls"), thisAttr->szAttrValue);
   jdeFprintf(pCbValues->fp, _J("\""));
  }
  indentNewLine(pCbValues);
 }
 /* display close of element name */
 jdeFprintf(pCbValues->fp, _J(">"));
 return( XRCS_CB_CONTINUE);
}

XRCS_CallbackStatus cb_endElement_Display_Terminate(void *pContext,
 const JCHAR *szUri,
 const JCHAR *szLocalname,
 const JCHAR *szQname)
{
 PCALLBACK_VALUES pCbValues = (PCALLBACK_VALUES) pContext;

 indentNewLine(pCbValues);
 jdeFprintf(pCbValues->fp, _J("END_ELM: "));
 if (jdeStrlen( szLocalname) != 0)
 {
  jdeFprintf(pCbValues->fp, _J("</%ls>"), szLocalname);
 }
 else
 {
  jdeFprintf(pCbValues->fp, _J("</%ls>"), szQname);
 }
 pCbValues->nIndentLevel--;
 return( XRCS_CB_TERMINATE);
}

XRCS_CallbackStatus cb_endElement_Display(void *pContext,
 const JCHAR *szUri,
 const JCHAR *szLocalname,
 const JCHAR *szQname)
{
 PCALLBACK_VALUES pCbValues = (PCALLBACK_VALUES) pContext;

 indentNewLine(pCbValues);
 jdeFprintf(pCbValues->fp, _J("END_ELM: "));
 if (jdeStrlen( szLocalname) != 0)
 {
  jdeFprintf(pCbValues->fp, _J("</%ls>"), szLocalname);
 }
 else
 {
  jdeFprintf(pCbValues->fp, _J("</%ls>"), szQname);
 }
 pCbValues->nIndentLevel--;
 return( XRCS_CB_CONTINUE);
}

XRCS_CallbackStatus cb_warning_Display(void *pContext,
 XRCS_CallbackType eCallbackType,
 int nLineNum,
 int nColNum,
 const JCHAR *szPublicId,
 const JCHAR *szSystemId,
 const JCHAR *szMessage)
{
 PCALLBACK_VALUES pCbValues = (PCALLBACK_VALUES) pContext;

 indentNewLine(pCbValues);
 jdeFprintf(pCbValues->fp, _J("Warning: "));
 jdeFprintf(pCbValues->fp, _J(" %ls (%ls) - %ls found at Column %d
 Line %d"), szSystemId, szPublicId, szMessage, nColNum, nLineNum);
 return( XRCS_CB_CONTINUE);
}

XRCS_CallbackStatus cb_error_Display(void *pContext,
 XRCS_CallbackType eCallbackType,
 int nLineNum,
 int nColNum,
 const JCHAR *szPublicId,
 const JCHAR *szSystemId,
 const JCHAR *szMessage)
{
 PCALLBACK_VALUES pCbValues = (PCALLBACK_VALUES) pContext;

 indentNewLine(pCbValues);
 jdeFprintf(pCbValues->fp, _J("Error: "));
 jdeFprintf(pCbValues->fp, _J(" %ls (%ls) - %ls found at Column %d
 Line %d"), szSystemId, szPublicId, szMessage, nColNum, nLineNum);
 return( XRCS_CB_CONTINUE);
}

XRCS_CallbackStatus cb_fatalError_Display(void *pContext,
 XRCS_CallbackType eCallbackType,
 int nLineNum,
 int nColNum,
 const JCHAR *szPublicId,
 const JCHAR *szSystemId,
 const JCHAR *szMessage)
{
 PCALLBACK_VALUES pCbValues = (PCALLBACK_VALUES) pContext;

 indentNewLine(pCbValues);
 jdeFprintf(pCbValues->fp, _J("Fatal Error: "));
 jdeFprintf(pCbValues->fp, _J(" %ls (%ls) - %ls found at Column %d Line %d"), 
 szSystemId, szPublicId, szMessage, nColNum, nLineNum);
 return( XRCS_CB_TERMINATE);
}

XRCS_CallbackStatus cb_characters_Display(void *pContext,
 const JCHAR *szText)
{
 PCALLBACK_VALUES pCbValues = (PCALLBACK_VALUES) pContext;
 int nTextLen;
 int nTextRemaining;
 int nTextPieceLen;
 int nTextStartPosition;

 nTextLen = jdeStrlen( szText);
 indentNewLine(pCbValues);
 jdeFprintf(pCbValues->fp, _J("CHARS: "));
 if (hasPrintingChars( szText, nTextLen) == TRUE)
 {
  /* initial quote */
  jdeFprintf(pCbValues->fp, _J("\""), szText);
  /* actual text, output in blocks of 10000 characters */
  /* jdeFprintf will not work with very large strings */
  nTextRemaining = nTextLen;
  nTextStartPosition = 0;
  while (nTextRemaining > 0)
  {
   if (nTextRemaining > 10000)
   {
   nTextPieceLen = 10000;
   }
   else
   {
   nTextPieceLen = nTextRemaining;
   }
   jdeFprintf(pCbValues->fp, _J("%.*ls"), nTextPieceLen,
   (JCHAR *) &(szText[nTextStartPosition]));
   nTextRemaining -= nTextPieceLen;
   nTextStartPosition += nTextPieceLen;
  }
  /* trailing quote */
  jdeFprintf(pCbValues->fp, _J("\""), szText);
 }
 return( XRCS_CB_CONTINUE);
}

XRCS_CallbackStatus cb_ignorableWhitespace_Display(void *pContext,
 const JCHAR *szText)
{
 PCALLBACK_VALUES pCbValues = (PCALLBACK_VALUES) pContext;
 int nTextLen;

 nTextLen = jdeStrlen( szText);
 indentNewLine(pCbValues);
 jdeFprintf(pCbValues->fp, _J("IGNORABLE WHITESPACE: "));
 if (hasPrintingChars( szText, nTextLen) == TRUE)
 {
  jdeFprintf(pCbValues->fp, _J("\"%ls\""), szText);
 }
 return( XRCS_CB_CONTINUE);
}

void indentNewLine(PCALLBACK_VALUES pCbValues)
{
 int nIndent = 0;

 jdeFprintf(pCbValues->fp,
  _J("\n"));

 while (nIndent < pCbValues->nIndentLevel)
 {
  jdeFprintf(pCbValues->fp, _J("%ls"), pCbValues->szIndentString);
  nIndent++;
 }
}

BOOL hasPrintingChars( const JCHAR *szText, int nTextLen)
{
 BOOL bHasPrinting = FALSE;
 int nText = 0;

 /* true if contains any printing characters */
 /* false if all blanks or control characters */
 while (nText < nTextLen)
 {
  if (szText[nText] > _J(' '))
  {
   bHasPrinting = TRUE;
   break;
  }
  nText++;
 }
 return( bHasPrinting);
}

2.3.3 Example of a SAX Parsing Sequence

This is an example of the sequence of callback functions called, for an example string of XML data. Before parsing, these callback functions were set up:

  • cb_startAllElements for start-of-element event type.

  • cb_endAllElements for end-of-element event type.

  • cb_startElement1 for start-of-element, with optional name specified as "elapsedTime."

  • cb_endElement1 for end-of-element, with optional name specified as "elapsedTime."

  • cb_chars for characters event type.

  • cb_allCharacters for characters, with optional setting for characters after elements.

  • cb_fatalError for fatal-error event type.

The example XML string to be parsed is:

<main>startMain<elapsedTime>123</elapsedTime>endMain</main>

This callback sequence results from parsing this XML string:

  • cb_startAllElements for main.

  • cb_chars for startMain.

  • cb_allCharacters for startMain.

  • cb_startAllElements for elapsedTime.

  • cb_startElement1 for elapsedTime.

  • cb_chars for 123.

  • cb_allCharacters for 123.

  • cb_endAllElements for elapsedTime.

  • cb_endElement1 for elapsedTime.

  • cb_allCharacters for endMain.

  • cb_endAllElements for main.

  • cb_fatalError is not called while parsing this example XML string.

2.4 Working with JDECACHE

This section provides overviews of caching, JDECACHE standards, and the JDECACHE API set, and discusses how to:

  • Call JDECACHE APIs.

  • Set up indices.

  • Initialize the cache.

  • Use an index to access the cache.

  • Use the jdeCacheInit/jdeCacheTerminate rule.

  • Use the same cache in multiple business functions or forms.

2.4.1 Understanding Caching

Caching is a process that stores a local copy of frequently accessed content of remote objects. Caching can improve performance. JD Edwards EnterpriseOne software caches information in these ways:

  • The system automatically caches some tables, such as those associated with constants, when it reads them from the database at startup.

    It caches these tables to a user's workstation or to a server for faster data access and retrieval.

  • Individual applications can be enabled to use cache.

    JDECACHE APIs enable the server or workstation memory to be used as temporary storage.

JDECACHE is a component of JDEKRNL that can hold any type of indexed data that the application needs to store in memory, regardless of the platform on which the application is running; therefore, an entire table can be read from a database and stored in memory. No limitations exist regarding the type of data, size of data, or number of data caches that an application can have, other than the limitations of the computer on which it is running. Both fixed-length and variable-length records are supported. To use JDECACHE on any supported platform, you need to know only a simple set of API calls.

Data handled by JDECACHE is in RAM. Therefore, ensure that you really need to use JDECACHE. If you use JDECACHE, design the records and indices carefully. Minimize the number of records that you store in JDECACHE because JD Edwards EnterpriseOne software and various other applications need this memory as well.

JDECACHE supports multiple cursors, multiple indexes, and partial keys processing. JDECACHE is flexible in terms of positioning within the cache for data manipulation, which improves performance by reducing searching within the cache.

The JDB environment creates, manages, and destroys the JDECACHE environment. Each cache that you use within the JDECACHE environment is associated with a JDB user. Therefore, you must call JDB_InitBhvr API before you call any of the JDECACHE APIs.

2.4.1.1 When to Use JDECACHE

Here is a scenario that highlights when an application might use the JDECACHE APIs.

You use workfiles when an application must store records that a user enters in a detail area until OK processing is activated upon the Button Clicked event. On OK processing, all records must be simultaneously updated to the database. This is similar to transaction processing. For example, in the detail area of purchase order detail, if a user enters 30 lines of information and then decides to cancel the transaction, all records in the workfile are deleted and nothing is written to the database. As the user exits each detail row, editing takes place for each field, and then that record is written to the workfile.

If you implement this situation without using workfiles, irreversible updates to database tables occur when the user exits each row. Using workfiles enables you to limit updates to tables so that they only occur on OK button processing, and they are included in a transaction boundary. The workfile defines a data boundary for the grid for processing purposes. This is useful when multiple applications or processes (such as business functions) must access the data in the workfile for updates and calculations.

Using cache might increase performance in some cases. You can use JDECACHE to store in memory the records that the user enters in one purchase order. The number of records that you store depends on the cache buffer size for each record, the local memory size, the location in which the business function that you use runs (for example, server or workstation), and so on. Typically, you should not store more than 1000 records. For example, do not cache the entire Address Book table in memory.

2.4.1.2 Performance Considerations

Follow these guidelines to get the best JDECACHE performance:

  • Cache as few records as possible.

  • The fewer columns (segments) that you use, the faster the search, insert, and delete actions occur.

    In some cases, the system might have to compare each column before it determines whether to go further in the cache.

  • The fewer records in the cache, the faster all operations proceed.

2.4.2 Understanding the JDECACHE API Set

You use a set of public APIs to interact with JDECACHE. You must understand how the JDECACHE APIs are organized to implement them effectively.

2.4.2.1 JDECACHE Management APIs

You can manage cache using the JDECACHE management APIs for these purposes:

  • Setting up the cache.

  • Clearing the cache.

  • Terminating the cache.

Use the jdeCacheGetNumRecords and jdeCacheGetNumCursors APIs to retrieve cache statistics. They are only passed the HCACHE handle. All other JDECACHE management APIs should always be passed these handles:

  • HUSER

  • HCACHE

These two handles are essential for cache identification and cache management.

The set of JDECACHE management APIs consist of these APIs:

  • jdeCacheInit

  • jdeCacheInitEx

  • jdeCacheInitMultipleIndex

  • jdeCacheInitMultipleIndexEx

  • jdeCacheInitUser

  • jdeCacheInitMultipleIndexUser

  • jdeCacheGetNumRecords

  • jdeCacheGetNumCursors

  • jdeCacheClear

  • jdeCacheTerminate

  • jdeCacheTerminateAll

The jdeCacheInit and jdeCacheInitMultipleIndex APIs initialize the cache uniquely per user. Therefore, if a user logs in to the software and then runs two sessions of the same application simultaneously, the two application sessions will share the same cache. Consequently, if the first application deletes a record from the cache, the second application cannot access the record. Conversely, if two users log in to the software and then run the same application simultaneously, the two application sessions have different caches. Consequently, if the first application deletes a record from its cache, the second application will still be able to access the record in its own cache. The jdeCacheInitEx and jdeCacheInitMultipleIndexEx APIs function exactly the same, but they additionally enable you to define the maximum number of cursors that can be opened by the cache.

The jdeCacheInitUser and jdeCacheInitMultipleIndexUser APIs initialize the cache uniquely per application. Therefore, if a user logs in to the software and then runs two sessions of the same application simultaneously, the two application sessions will have different caches. Consequently, if the first application deletes a record from its cache, the second application can still access the record in its own cache.

2.4.2.2 JDECACHE Manipulation APIs

You can use the JDECACHE manipulation APIs for retrieving and manipulating the data in the cache. Each API implements a cursor that acts as pointer to a record that is currently being manipulated. This cursor is essential for navigation within the cache. JDECACHE manipulation APIs should be passed handles of these types:

  • HCACHE

    Identifies the cache that is being worked.

  • HJDECURSOR

    Identifies the position in the cache that is being worked.

The set of JDECACHE manipulation APIs contain these APIs:

  • jdeCacheOpenCursor

  • jdeCacheResetCursor

  • jdeCacheAdd

  • jdeCacheFetch

  • jdeCacheFetchPosition

  • jdeCacheUpdate

  • jdeCacheDelete

  • jdeCacheDeleteAll

  • jdeCacheCloseCursor

  • jdeCacheFetchPositionByRef

  • jdeCacheSetIndex

  • jdeCacheGetIndex

2.4.3 Understanding JDECACHE Standards

It is recommended that you apply several standards when using JDECACHE. This section discusses the standards for business functions and programming.

The cache business function name should follow the standard naming convention for business functions.

2.4.3.1 Cache Business Function Source Description

These standards apply to source descriptions for cache business functions:

  • The cache business function description must follow the business function description standards.

  • The first word must be the noun, Cache.

  • The second word must be the verb, Process.

  • For an individual cache function, the words following Process should describe the cache. For a common cache function, the words following Process should describe the group to which the individual cache functions belong.

These standards apply to cache business function descriptions:

  • If the source file contains an individual function, the function name must match the source name.

  • If the source file contains a group of cache functions, the individual function names must follow the same standards as the Cache Business Function Source Description standards.

2.4.3.2 Cache Programming Standards

A variety of cache programming standards apply:

  • General standards.

  • Cache termination instead of clearing.

  • Cache name.

  • Cache data structure definition.

  • Data structure standard data items.

  • Cache action code standards.

  • Group cache business function header file.

  • Individual cache business function header file.

2.4.4 Prerequisites

Before you can use JDECACHE, you must:

  • Define an index

    The index specifies to the cache the fields in a record that are used to uniquely identify a cache record.

  • Initialize a cache

    Each group of data that an index references requires a separate cache.

2.4.5 Calling JDECACHE APIs

JDECHACHE APIs must be called in a certain order. This list defines the order in which the JDECACHE-related APIs must be called:

  1. Call JDB_InitBhvr.

  2. Create index or indices.

  3. Call jdeCacheInit, jdeCacheInitEx, jdeCacheInitMultipleIndex, or jdeCacheInitMultipleIndexEx.

  4. Call jdeCacheAdd.

  5. Call jdeCacheOpenCursor.

  6. Call JDECACHE Operations.

At JDECACHE Operations, the actual JDECACHE APIs can be called in any order. The operations in this list of JDECACHE operations can occur in any order:

  • jdeCacheFetch

  • jdeCacheOpenCursor (the second cursor)

  • jdeCacheFetchPosition

  • jdeCacheUpdate

  • jdeCacheDelete

  • jdeCacheDeleteAll

  • jdeCacheResetCursor

  • jdeCacheCloseCursor (if the second cursor is opened)

  • jdeCacheCloseCursor

  • jdeCacheTerminate

  • JDB_FreeBhvr

2.4.6 Setting Up Indexes

To store or retrieve any data in JDECACHE, you must set up at least one index that consists of at least one column. The index is limited to a maximum of 25 columns (which are called segments) in the index structure. Use the data type provided to tell the cache manager what the index looks like. You must provide the number of columns (segments) in the index and the offset and size of each column in the data structure. To maximize performance, minimize the number of segments.

This code is the definition of the structure that holds index information:

#define JDECM_MAX_UM_SEGMENTS 25
struct _JDECMKeySegment
{
 short int nOffset;  /* Offset from beginning of structure in bytes */
 short int nSize;  /* Size of data item in bytes */
 int idDataType;  /* EVDT_MATH_NUMERIC or EVDT_STRING*/
} JDECMKEYSEGMENT;
struct _JDECMKeyStruct
{
 short int nNumSegments;
 JDECMKEYSEGMENT CacheKey[JDECM_MAX_NUM_SEGMENTS];
} JDECMINDEXSTRUCT;

Observe these rules when you create indices in JDECACHE:

  • Always declare the index structure as an array that holds one element for single indexes.

    Declare the index structure as an array that holds more than one element for multiple indexes. You can create an unlimited number of indexes.

  • Always use memset() for the index structure.

    When you use memset() for multiple indexes, multiply the size of the index structure by the total number of indexes.

  • Always assign as elements the number of segments that correspond to the number of columns that you have in the CacheKey array.

  • Always use offsetof () to indicate the offset of a column in the structure that contains the columns.

This example illustrates a single index with multiple fields:

/* Example of single index with multiple fields.*/
JDECMINDEXSTRUCT Index[1]    = {0};
memset(&dsCache,0x00,sizeof(dsCache));
/* Initialize cache. */
Index->nNumSegments=5;
Index->CacheKey[0].nOffset=offsetof(DSCACHE,szEdiUserId);
Index->CacheKey[0].nSize=DIM(dsCache.szEdiUserId);
Index->CacheKey[0].idDataType=EVDT_STRING;
Index->CacheKey[1].nOffset=offsetof(DSCACHE,szEdiBatchNumber);
Index->CacheKey[1].nSize=DIM(dsCache.szEdiBatchNumber);
Index->CacheKey[1].idDataType=EVDT_STRING;
Index->CacheKey[2].nOffset=offsetof(DSCACHE,szEdiTransactNumber);
Index->CacheKey[2].nSize=DIM(dsCache.szEdiTransactNumber);
Index->CacheKey[2].idDataType=EVDT_STRING;
Index->CacheKey[3].nOffset=offsetof(DSCACHE,mnEdiLineNumber);
Index->CacheKey[3].nSize=sizeof(dsCache.mnEdiLineNumber);
Index->CacheKey[3].idDataType=EVDT_MATH_NUMERIC;
Index->CacheKey[4].nOffset=offsetof(DSCACHE.cErrorCode);
Index->CacheKey[4].nSize = 1;
Index->CacheKey[4].idDataType=EVDT_CHAR

The flag, idDataType, indicates the data type of the particular key.

This example illustrates a cache with multiple indices and multiple fields:

Memset(jdecmIndex,0x00,sizeof(JDECMINDEXSTRUCT)*2);
jdecmIndex[0].nKeyID=1;
jdecmIndex[0].nNumSegments=6;
jdecmIndex[0].CacheKey[0].nOffset=offsetof(I1000042,szCostCenter);
jdecmIndex[0].CacheKey[0].nSize=DIM(dsI1000042.szCostCenter);
jdecmIndex[0].CacheKey[0].idDataType=EVDT_STRING;
jdecmIndex[0].CacheKey[1].nOffset=offsetof(I1000042,szObjectAccount);
jdecmIndex[0].CacheKey[1].nSize=DIM(dsI1000042.szObjectAccount);
jdecmIndex[0].CacheKey[1].idDataType=EVDT_STRING;
jdecmIndex[0].CacheKey[2].nOffset=offsetof(I1000042,szSubsidiary);
jdecmIndex[0].CacheKey[2].nSize=DIM(dsI1000042.szSubsidiary);
jdecmIndex[0].CacheKey[2].idDataType=EVDT_STRING;
jdecmIndex[0].CacheKey[3].nOffset=offsetof(I1000042,szSubledger);
jdecmIndex[0].CacheKey[3].nSize=DIM(dsI1000042.szSubledger);
jdecmIndex[0].CacheKey[3].idDataType=EVDT_STRING;
jdecmIndex[0].CacheKey[4].nOffset=offsetof(I1000042,szSubledgerType);
jdecmIndex[0].CacheKey[4].nSize=1;
jdecmIndex[0].CacheKey[4].idDataType=EVDT_STRING;
jdecmIndex[0].CacheKey[5].nOffset=offsetof(I1000042,szCurrencyCodeFrom);
jdecmIndex[0].CacheKey[5].nSize=DIM(dsI1000042.szCurrencyCodeFrom);
jdecmIndex[0].CacheKey[5].idDataType=EVDT_STRING;
************************ KEY 2 *******************************
jdecmIndex[1].nKeyID=2;
jdecmIndex[1].nNumSegments=7;
jdecmIndex[1].CacheKey[0].nOffset=offsetof(I1000042,szEliminationGroup);
jdecmIndex[1].CacheKey[0].nSize=DIM(dsI1000042.szEliminationGroup);
jdecmIndex[1].CacheKey[0].idDataType=EVDT_STRING;
jdecmIndex[1].CacheKey[1].nOffset=offsetof(I1000042,szCostCenter);
jdecmIndex[1].CacheKey[1].nSize=DIM(dsI1000042.szCostCenter);
jdecmIndex[1].CacheKey[1].idDataType=EVDT_STRING;
jdecmIndex[1].CacheKey[2].nOffset=offsetof(I1000042,szObjectAccout);
jdecmIndex[1].CacheKey[2].nSize=DIM(dsI1000042.szObjectAccount);
jdecmIndex[0].CacheKey[2].idDataType=EVDT_STRING;
jdecmIndex[1].CacheKey[3].nOffset=offsetof(I1000042,szSubsidiary);
jdecmIndex[1].CacheKey[3].nSize=DIM(dsI1000042.szSubsidiary);
jdecmIndex[1].CacheKey[3].idDataType=EVDT_STRING;
jdecmIndex[1].CacheKey[4].nOffset=offsetof(I1000042,szSubledger);
jdecmIndex[1].CacheKey[4].nSize=DIM(dsI1000042.szSubledger);
jdecmIndex[1].CacheKey[4].idDataType=EVDT_STRING;
jdecmIndex[1].CacheKey[5].nOffset=offsetof(I1000042,szSubledgerType);
jdecmIndex[1].CacheKey[5].nSize=1;
jdecmIndex[1].CacheKey[5].idDataType=EVDT_STRING;
jdecmIndex[1].CacheKey[6].nOffset=offsetof(I1000042,szCurrencyCodeFrom);
jdecmIndex[0].CacheKey[6].nSize=DIM(dsI1000042.szCurrencyCodeFrom);
jdecmIndex[0].CacheKey[6].idDataType=EVDT_STRING;

2.4.7 Initializing the Cache

After you set up the index or indices, call jdeCacheInit, jdeCacheInitEx, jdeCacheInitMultipleIndex, or jdeCacheInitMultipleIndexEx. to initialize (create) the cache. Pass a unique cache name so that JDECACHE can identify the cache. Pass the index to this API so that the JDECACHE knows how to reference the data that will be stored in the cache. Because each cache must be associated with a user, you must also pass the user handle obtained from the call to JDB_InitUser. This API returns an HCACHE handle to the cache that JDECACHE creates. This handle appears in every subsequent JDECACHE API to identify the cache.

The keys in the index must be identical for every jdeCacheInit, jdeCacheInitEx, jdeCacheInitMultipleIndex, and jdeCacheInitMultipleIndexEx call for that cache until it is terminated. The keys in the index must correspond in number, order, and type for that index each time that it is used.

After the cache has been initialized successfully, JDECACHE operations can take place using the JDECACHE APIs. The cache handle obtained from jdeCacheInit or jdeCacheInitEx must be passed for every JDECACHE operation. JDECACHE makes an internal Index Definition Structure that accesses the cache when it is populated.

2.4.7.1 Example: Index Definition Structure

In this scenario, assume that each record that the cache stores has this structure:

int nlnt1
JCHAR cLetter1
JCHAR cLetter2
JCHAR cLetter3
JCHAR szArray(5)

The next step is to determine which values to use to index each record in the cache uniquely. In this example, assume that these values are required:

  • nInt1

  • cLetter1

  • cLetter3

Pass that information to jdeCacheInit or jdeCacheInitEx, and JDECACHE creates this Index Definition Structure for internal use. This table lists Index Definition Structure is for STRUCT letters:

Index Key No. Index Key Offset Index Key Offset INTEGER
Index Key #1 0 INTEGER
Index Key #2 4 JCHAR
Index Key #3 6 JCHAR

2.4.8 Using an Index to Access the Cache

When you use an index to access the cache, the keys in the index that are sent to the API must correspond to the keys of the index used in the call to jdeCacheInit or jdeCacheInitEx for that cache in number, order, offset positions, and type. Therefore, if a field that was used in the index passed to jdeCacheInit or jdeCacheInitEx offsets position 99, it must also offset position 99 in the index structure that passed to JDECACHE access API.

You should use the same index structure that was used for the call to jdeCacheInit or jdeCacheInitEx whenever you call an API that requires an index structure.

The next example illustrates why the index offsets must be specified for the jdeCacheInit or jdeCacheInitEx and how they are used when a record is to be retrieved from the cache. It describes how the passed key is used in conjunction with the JDECACHE internal index definition structure to access cache records.

2.4.8.1 Example: JDECACHE Internal Index Definition Structure

In this example, assume that the user is looking for a record that matches these index key values:

  • 1

  • c

  • i

JDECACHE accesses the values that you pass in the structure at the byte offsets that were defined in the call to jdeCacheInit or jdeCacheInitEx.

JDECACHE compares the values 1, c, and i that it retrieves from the passed structure to the corresponding values in each of the cache records at the corresponding byte offset. The cache records are stored as the structures that were inserted into the cache by jdeCacheAdd, which is the same structure as the one you pass first. The structure that matches the passed key is the second structure to which HCUR1 points.

You should never create a smaller structure that contains just the key to access the cache. Unlike most indexing systems, JDECACHE does not store a cache record's index separately from the actual cache record. This is because JDECACHE deals with memory-resident data and is designed to be as memory-conservative as possible. Therefore, JDECACHE does not waste memory by storing an extra structure for the sole purpose of indexing. Instead, a JDECACHE record has a dual purpose of index storage and data storage. This means that, when you retrieve a record from JDECACHE using a key, the key should be contained in a structure that is of the same type as the structure that is used to store the record in the cache.

Do not use any key structure to access the cache other than the one for which offsets that were defined in the index passed to jdeCacheInit or jdeCacheInitEx. The structure that contains the keys when accessing a cache should be the same structure that is used to store the cache records.

If jdeCacheInit or jdeCacheInitEx is called twice with the same cache name and the same user handle without an intermediate call to jdeCacheTerminate, the cache that was initialized using the first jdeCacheInit or jdeCacheInitEx will be retained. Always call jdeCacheInit or jdeCacheInitEx with the same index each time that you call it with the same cache name. If you call jdeCacheInit or jdeCacheInitEx for the same cache with a different index, none of the JDECACHE APIs will work.

The key for searches must always use the same structure type that stores cache records.

2.4.9 Using the jdeCacheInit/jdeCacheTerminate Rule

For every jdeCacheInit, jdeCacheInitEx, jdeCacheInitMultipleIndex, or jdeCacheInitMultipleIndexEx, a corresponding jdeCacheTerminate must exist, except instances in which the same cache is used across business functions or forms. In this case, all unterminated jdeCacheInit, jdeCacheInitEx, jdeCacheInitMultipleIndex, or jdeCacheInitMultipleIndexEx calls must be terminated with a jdeCacheTerminateAll.

A jdeCacheTerminate call terminates the most recent corresponding jdeCacheInit or jdeCacheInitEx. This means that the same cache can be used in nested business functions. In each function, perform a jdeCacheInit or jdeCacheInitEx or jdeCacheInitEx that passes the cache name. Before exiting that function, call jdeCacheTerminate. This does not destroy the cache. Instead, it destroys the association between the cache and the passed HCACHE handle. The cache is completely destroyed from memory only when the number of jdeCacheTerminate calls matches the number of jdeCacheInit or jdeCacheInitEx calls. In contrast, one call to jdeCacheTerminateAll destroys the cache from memory regardless of the number of jdeCacheInit, jdeCacheInitEx, jdeCacheInitMultipleIndex, or jdeCacheInitMultipleIndexEx calls or jdeCacheTerminate calls.

2.4.10 Using the Same Cache in Multiple Business Functions or Forms

If the same cache is required for two or more business functions or forms, call jdeCacheInit or jdeCacheInitEx in the first business function or form, and add data to it. After exiting that business function or form, do not call jdeCacheTerminate because this removes the cache from memory. Instead, in the subsequent business functions or forms, call jdeCacheInit or jdeCacheInitEx again with the same index and cache name as in the initial call to jdeCacheInit or jdeCacheInitEx. Because the cache was not terminated the first time, JDECACHE looks for a cache with the same name and assigns that to you. Because the cache already has records in it, you do not need to refresh it. You can proceed with normal cache operations on that cache.

If a cache is initialized multiple times across business functions or forms, use jdeCacheTerminateAll to terminate all instances of the cache that were initialized. The name of the cache that corresponds to the HCACHE passed to this API will be used to determine the cache to destroy. Use this API when you do not want to call jdeCacheTerminate for the number of times that jdeCacheInit or jdeCacheInitEx was called. If you move from one form or business function to another when you initialize the same cache across business functions or forms, you will lose the HCACHE because it is a local variable. To share the same cache across business functions or forms, do not call jdeCacheTerminate when you exit a form or business function if you intend to use the same cache in another form or business function.

2.5 Working with JDECACHE Cursors

JDECACHE Cursors (JDECACHE Cursor Manager) is a component of JDECACHE that implements a JDECACHE cursor for record retrieval and update. A JDECACHE cursor is a pointer to a record in a user's cache. The record after the record in which the cursor is currently pointing is the next record that will be retrieved from the cache upon calling a cache fetch API.

This section discusses how to:

  • Open a JDECACHE cursor.

  • Use the JDECACHE data set.

  • Update records.

  • Delete records.

  • Use the jdeCacheFetchPosition API.

  • Use the jdeCacheFetchPostionByRef API.

  • Reset the cursor.

  • Close the cursor.

  • Use JDECACHE multiple cursor support.

  • Use JDECACHE partial keys.

2.5.1 Opening a JDECACHE Cursor

Manipulating the JDECACHE data is cursor-dependent. Before the JDECACHE data manipulation APIs will work, a cursor must be opened. A cursor must be opened to obtain a cursor handle of the type HJDECURSOR, which must, in turn, be passed to all of the JDECACHE data manipulation APIs (with the exception of the jdeCacheAdd API). HJDECURSOR is the data type for the cursor handle. It must be passed to every API for JDECACHE data manipulation except jdeCacheAdd.

To open the cursor, call the jdeCacheOpenCursor API. A call to this API also makes possible the calls to all the data manipulation APIs (except for jdeCacheAdd). If you do not open the cursor, these APIs will not work. With this call, the cursor opens a JDECACHE data set, within which it will work. This API opens the data set, but does not fetch any data. This means that the cache must be initialized by a call to jdeCacheInit or jdeCacheInitEx and populated by a call to jdeCacheAdd before a cursor can be opened.

You can obtain multiple cursors to a cache by calling jdeCacheOpenCursor and passing different HJDECURSOR handles. In a multiple cursor environment, all the cursors are independent of each other.

When you are finished working with the cursor, you must deactivate it or close it by calling the jdeCacheCloseCursor API, and passing an HJDECURSOR handle that corresponds to the HJDECURSOR handle that was passed to the jdeCacheOpenCursor. When a cursor is closed, it cannot be used again until it is opened by a call to jdeCacheOpenCursor.

2.5.2 Using the JDECACHE Data Set

The JDECACHE data set includes all of the records from the current position of the cursor to the end of the set of sequenced records. Thus, if a cursor is in the middle of the data set, none of the records in the cache prior to the current position of the cursor is considered part of the data set. The JDECACHE data set consists of the cache records sequenced in ascending order of the given index keys. This means that the order in which the records have been placed in JDECACHE is not necessarily the order in which JDECACHE Cursors retrieves them. JDECACHE Cursors retrieves records in a sequential ascending order of the index keys. A forward movement by the cursor reduces the size of the data set during sequential retrievals. When the cursor advances past the last record in the data set, a failure is returned.

This example illustrates the creation of a JDECACHE cache and a JDECACHE data set:

Figure 2-1 Example of JDECACHE cache and data set creation

Description of Figure 2-1 follows
Description of "Figure 2-1 Example of JDECACHE cache and data set creation"

2.5.2.1 Cursor-Advancing APIs

Cursor-advancing JDECACHE fetch APIs implement the fundamental concepts of a cursor. The cursor-advancing API set consists of APIs that advance the cursor to the next record in the JDECACHE data set before fetching a record from JDECACHE. jdeCacheFetch and jdeCacheFetchPosition are examples of cursor-advancing fetch APIs.

A call to jdeCacheFetch first positions the cursor at the next record in the JDECACHE data set before retrieving it. JDECACHE Cursors also enable calls to position the cursor at a specific record within the data set. To do this, you call the jdeCacheFetchPosition API, which advances the cursor to the record that matches the given key before retrieving it.

You can use a combination of cursor-advancing fetch APIs if you need a sequential fetch of records starting from a certain position. Call jdeCacheFetchPosition, passing the key of the record from which you want to start retrieving. This advances the cursor to the desired location in the data set and retrieves the record. All subsequent calls to jdeCacheFetch will fetch records starting from the current cursor position in the data set until the end of the data set, or until the program stops for another reason.

2.5.2.2 Non-Cursor-Advancing APIs

Non-cursor-advancing JDECACHE cursor APIs do not advance the cursor before retrieving a record. Instead, they keep the cursor pointing to the retrieved record. jdeCacheUpdate and jdeCacheDelete are examples of non-cursor-advancing fetch APIs.

2.5.3 Updating Records

If you want to update a specific record with a key that you know, call jdeCacheFetchPosition, passing the known key, to position the cursor at the location of the record that matches the key. Because the cursor is already pointing to the desired location, call jdeCacheUpdate, passing the same HJDECURSOR that you used in the call to jdeCacheFetchPosition.

If the index key changes, cache resorts the records, and the cursor points to the updated location. However, when you call jdeCacheFetch, the system retrieves the next record in the updated set. Consequently, the system might not retrieve the correct record because the changed index key caused the order of the records to change.

To update a sequential number of records, make a call to jdeCacheFetchPosition to return to the beginning of the sequence, if necessary. Then call jdeCacheUpdate, passing the same HJDECURSOR that you used in the call to jdeCacheFetchPosition. This call updates only the record to which the cursor is pointing. To update the rest of the records in the sequence, call jdeCacheFetch repeatedly, passing the same HJDECURSOR that you used in the call to jdeCacheFetchPosition, until you get to the end of the sequence. A sequential update will not work correctly if you have changed any index key value. However, a sequential update will work correctly if you are updating a value that is not an index key.

2.5.4 Deleting Records

If you want to delete a specific record with a known key, first call jdeCacheFetchPosition to point the cursor to the location of the record that matches the key. Next, call jdeCacheDelete, to remove the record from cache. Pass jdeCacheDelete the same HJDECURSOR that you used when you called jdeCacheFetchPosition. After deleting a record, use jdeCacheFetch to retrieve the record that followed the now-deleted record. This process works only when you call jdeCacheDelete.

You can also delete a specific record by calling jdeCacheDeleteAll and passing it the full key with the specific record to be deleted. In this case, jdeCacheFetch will not work following jdeCacheDeleteAll, although you can work around this condition with jdeCacheFetchPosition or jdeCacheResetCursor.

To delete a sequential set of records, first call jdeCacheFetchPosition to point the cursor to the first record in the set or call jdeCacheDeleteAll to delete the first record in the set. Then, call jdeCacheDelete sequentially. In this case, jdeCacheFetch will not work following jdeCacheDeleteAll, although you can work around this condition with jdeCacheFetchPosition or jdeCacheResetCursor.

If you want to delete records that match a partial key, call jdeCacheDeleteAll and pass it a partial key. The system deletes all of the records that match the partial key. After you call this API, jdeCacheFetch does not work.

2.5.5 Using the jdeCacheFetchPosition API

The jdeCacheFetchPosition API searches for a specific record in the data set; therefore, it requires a specific key. This API can perform full and partial key searches.

Note:

If you pass 0 for the number of keys, the system assumes that you want to perform a full key search.

2.5.6 Using the jdeCacheFetchPositionByRef API

The jdeCacheFetchPositionByRef API returns the address of a data set. The API finds the one record in cache and returns a reference (pointer) to the data. jdeCacheFetchPositionByRef retrieves a single, large block of data that is stored in cache. If the cache is empty or has more than one record, this API fails.

2.5.7 Resetting the Cursor

JDECACHE cursors supports multiple cursors, as well as an unlimited number of cursor oscillations within the data set. This means that the cursor can shuttle from beginning to end for an unlimited number of times. The cursor moves forward only. To reset the cursor (move the cursor back to the beginning of the data set), you must make a call to the jdeCacheResetCursor API to get a fresh JDECACHE data set.

You can also reset a cursor to a specific position that is outside of the current data set by calling the jdeCacheFetchPosition API.

2.5.8 Closing the Cursor

When you no longer need the cursor, call jdeCacheCloseCursor to close it. This call closes both the data set and the cursor. Any subsequent call to any JDECACHE API passing the closed HJDECURSOR without having called jdeCacheOpenCursor will fail.

Although opening a JDECACHE Cursor for a long period of time requires no overhead, to release the memory that it requires, you should close the cursor as soon as you no longer need it.

2.5.9 Using JDECACHE Multiple Cursor Support

JDECACHE supports multiple open cursors. Each cache that you initialize with jdeCacheInit or jdeCacheInitMultipleIndex enables up to 100 open cursors to access it at the same time. When you initialize a cache with jdeCacheInitEx or jdeCacheInitMultipleIndexEx, you can enable any number of cursors, between one and 100, to access it at the same time.

JDECACHE multiple cursors are designed to enable two or more asynchronously processing business functions to use one cache. Asynchronously processing business functions can open cursors to access the cache with relative positions within the cache that are independent of each other. A cursor movement by one business function does not affect any other open cursor.

Some JD Edwards EnterpriseOne software applications groups restrict the use of multiple cursors. For example, use multiple cursors only if you have a need for them. Additionally, do not use two cursors to point to the same record at the same time unless both cursors are fetching the record.

2.5.10 Using JDECACHE Partial Keys

A JDECACHE partial key is a subset of a JDECACHE key that is ordered in the same way as the defined index, beginning with the first key in the defined index. For example, for a defined index of N keys, the partial key is the subset of the keys 1, 2, 3, 4...N-1 in that specific order. The order is critical. Partial key components must appear in the same order as the key components in the index. (The index is passed to jdeCacheInit or jdeCacheInitEx.)

For example, suppose that an index is defined as a structure containing the fields in this order: A, B, C, D, E. The partial keys that can be synthesized from this index are this, in order: A, AB, ABC, ABCD. The previous set is the only set of partial keys that can be synthesized for the defined index: A, B, C, D, E.

A JDECACHE partial key implements the JDECACHE cursor. When you implement the JDECACHE partial key, consider that the JDECACHE cursor works within a JDECACHE data set, which comprises the records within the cache ordered by the defined index, the full index. If you call a jdeCacheFetchPosition API and pass the partial key, the JDECACHE cursor activates and points to the first record in the JDECACHE data set that matches the partial key. If a jdeCacheFetchPosition API was called, subsequent calls to jdeCacheFetch will fetch all of the records in the data set that succeed the fetched record to the end of the data set. The cursor does not stop on the last record that matches the partial key, but continues on to fetch the next record using the next call to jdeCacheFetch, even if it does not match the partial key. When a partial key is sent to jdeCacheFetchPosition, it merely indicates from where the JDECACHE begins fetching. Because the records in the JDECACHE data set are always ordered, the fetch always retrieves all of the records that satisfy the partial key first.

JDECACHE knows that you are passing a partial key because the fourth parameter to jdeCacheFetchPosition indicates the number of key fields that are in the key being sent to the API. If the number of key fields is less than the keys that were indicated when jdeCacheInit or jdeCacheInitEx was called, then it is a partial key. Suppose the number of keys is N so that JDECACHE uses the first N key fields to make comparisons in order to achieve the partial key functionality. If jdeCacheFetchPosition is called with a number of keys that is greater than the number specified on the call to jdeCacheInit or jdeCacheInitEx, an error is returned.

To delete a partial key, you must make a call to jdeCacheDeleteAll. This call deletes all of the records that match the partial key. To indicate to JDECACHE the partial keys that you are using, pass the number of key fields to this API.

Verify that the actual number of key fields in the structure corresponds to the numeric value that describes the number of keys that must be sent to either jdeCacheFetchPosition or jdeCacheDeleteAll.