Cookbook

Contents

Hooking into User Exits

Maintaining General-Purpose Maintenances

Maintaining MOs

Maintaining Database Meta-data

Maintaining Java Classes

Maintaining Services

Maintaining Foreign Key References

Maintaining Lookup Tables

Maintaining Navigation Keys

Maintaining Navigation Options

Maintaining User Interfaces

Maintaining Menus

Maintaining Application Security

Maintaining UI Components (Translation)

Plugging in Algorithms

Maintaining Portals and Zones

Maintaining Background Processes

Building the Application Viewer

Upgrade JSP to XSLT

Hooking into User Exits

 

Contents

Hooking into Maintenance Class User Exits

Hooking into UI Javascript User Exits

Hooking into Java User Exits (interceptors)

Hooking into Maintenance Class User Exits

 

Maintenance extensions

Not all maintenance logic can go in the initial application's Maintenance. For instance, how can you retrieve the description of a foreign key whose table doesn't exist in that application?

Therefore, an "extension" methodology needs to exist whereby an existing page can have behavior added to it at predetermined plug-in points.

This is done by having a list of maintenance extensions that can be supplied for any given maintenance. At runtime, this list is kept and when a maintenance is initialized, new instances of its extensions are created. These extensions are called after any original maintenance behavior, and in the order of loaded applications. This means that the extensions should have no dependence on which other extensions have run, excepting the original maintenance having run.

Developing Maintenance Extensions

Maintenance extensions must use the same buffer structure as the original maintenance. The only change allowed is to add possible new default values. Thus a maintenance extension with its annotation might look like this:

/**

 * @version $Revision: #3 $

 * @MaintenanceExtension (serviceName = CILTALTP,

 *      newDefaults={ @JavaNameValue (value = TEST, name = test)

 *                  }

 *      )

 */

public class AlgorithmTypeMaintenanceExtension

    extends AlgorithmTypeMaintenanceExtension_Gen {

   }

 

The maintenance extension will have its superclass generated to give easy access to the STRUCTURE definition and HEADER and DEFAULT constants, as well as provide an easy hook for any future functionality that might need to be inserted.

You must use the constants on the STRUCTURE or HEADER structure definitions to reference input header fields or which output fields to populate.

The maintenance extension can then override any methods needed to provide its functionality. Some examples of methods available are:

    /**

     * Process a default

     * @param defaultValue the raw string value of the default (can compare

     *     against DEFAULTS constants)

     * @param item the item to be modified with default values

     */

    public void processDefault(String defaultValue, DataElement item) {}

 

    /**

     * Process the data after the whole add (root and chidren) action is

     *     done.

     * @param originalItem the input item

     */

    public void afterAdd(DataElement originalItem) {}

 

    /**

     * Process the data after the whole read (root and children) action is

     *     done.

     * @param result the output item

     */

    public void afterRead(DataElement result) {}

 

 

    /**

     * Process the data after an element of the given list has been read.

     * @param listName the list name

     * @param outputElement the output element

     * @param sourceEntity the just read entity

     */

    public void afterPopulateElement(String listName,

        DataElement outputElement, BusinessEntity sourceEntity) {}

 

    /**

     * Process the data after an element of the given list has been changed.

     * @param listName the list name

     * @param inputElement the input element

     * @param changedEntity the changed entity

     */

    public void afterChangeElement(String listName,

        DataElement inputElement, BusinessEntity changedEntity) {}

 

    // ...

 

The complete list can be found in the hierarchy of the extension class (e.g., AbstractMaintenanceExtension) http://www.python.org/

Hooking into UI Javascript User Exits

The client-side external user exits are designed to give implementers flexibility and power to extend the  base package user interface.  Implementers have the ability to add additional business logic without changing  base html files.  These user exits were developed such that developers can create an include-like file based on external user exit templates.

There are two types of client user exits available.  There are process-based user exits that wrap the similar product user exit code with pre- and post- external user exit calls, and there are also data-based user exits that simply allow the implementer to add/delete data from the product returned data.

Both types of external user exit are only called if the function exists in the implementer’s external include JSP file.  All available user exits are listed online in the system through the relative URL: /code/availableUserExits.jsp, with definition examples and links to the Framework code that executes the call.

 

Miscellaneous How-To’s

The following are some how-to examples of typical behavior utilizing some of the standard user exits.

The examples are written for cases of modifying new CM transaction pages, where the function definitions are put into “extended JavaScipt” files (.xjs) that are meant to contain JavaScript user exits directly for a page.

If, on the other hand, an implementer wishes to modify the behavior of a shipped product page, each of the functions below have a corresponding “ext” function that can defined in a /cm/extXXX.jsp file corresponding to the desired page that will fire after any product function call (see above an example of hiding the Sequence column in the algorithm maintenance page).

Contents

How do I control the Initial Focus within Tab Pages/Grids/Search Pages?

How do I mark fields that won't make the model dirty?

How do I control the triggering of defaults after a search?

How do I avoid automatically setting fields to uppercase?

How Can I Force the Save Button to be Enabled?

How Can I Override the Processing After a Change/Add?

How Do I Prevent the System from Setting Focus to a Widget after an Error?

How Do I Prevent Attributes From Being Copied Into New List Elements?

How Do I Customize New List Elements?

How can I get my sequence numbers to default in an appropriate and consistent manner

How Do I Override the Tab Page Shown After An Error in a List (Grid/Scroll)?

How Do I Disregard Unwanted Criteria From a Search Triggered Search by a Search Butto

How Do I Disregard Unwanted Search Result Columns?

How do I format a value based on a given format?

How do I control the Initial Focus within Tab Pages/Grids/Search Pages?

The system automatically places the initial focus on an appropriate widget (generally input fields) within a Tab Page/Search Page/Grid.

By default it will place focus on the first enabled field with a data class defined on it. (If input fields do not have the Field Name / Table Name defined within Meta Data they will have no data class)

If there are no fields satisfying this criteria within the tab page it will continue to look (recursively) into all the contained frames (i.e. list grids etc.)

If no field is found then no element receives focus.

You can override the default behavior at each level via the provision of a focusWidgetOverride() function within the user exit file which will return the Name of the Field to receive the focus or null.

If null is returned it will ignore all fields within this document and continue to search in lower level documents.

E.G.

From within a Tab Page (If you want focus to go on to a sub document)

 

function focusWidgetOverride() {

  return null;

}

 

From within a List Grid

function focusWidgetOverride() {

  return "TD_TYPE_DRLKY:0$TBL_NAME";

}

 

from within a Search Page

function focusWidgetOverride() {

   return "LAST_NAME";

}

 

Note.  These functions can be as simple or complicated as you want.  You could conditionally return a field name or null and this code will run each time the window loads.  Also, if a tab page has a popup window or a search window open as it is loading then the initial focus will not be set to the tab page but stay with the popup window

How do I mark fields that won't make the model dirty?

In certain windows, we have a concept of a "locator" field, which typically acts as a filter on some lists of the object you're looking at. Examples are user group's filter on description, and several IB windows’ filter by date.

With the warning about loss of data when throwing away a dirty model, this results in the use of locator fields giving this warning, which wouldn't be expected. In order to avoid this warning on locator fields, you can add a function like the one that follows that enumerates the locator fields:

 

function ignoreModifiedFields(){

    return ['START_DTTM']

}

 

You can include any number of fields in the array, e.g.

return ['FIELD_1', 'FIELD_2', 'FIELD_3']

 

How do I control the triggering of defaults after a search?

If a search returns multiple fields and more than one of these fields can trigger default, then it might be more efficient to only have one of these fields trigger the defaulting.

This is accomplished by creating a new function called overrideDefaultTriggersFor_SEARCHGROUP within the tab page that contains the search, where SEARCHGROUP is the name of the searchGroup you want to override.

The function must return an object with the triggering field(s) are attributes with a true value.

For example

 

function overrideDefaultTriggersFor_SRCH1() {

   var triggers = {};

 

   triggers["ACCT_ID"] = true;

            triggers["SA_ID"]=true;

 

   return triggers;

}

 

How do I avoid automatically setting fields to uppercase?

Model attributes that are also key fields are automatically coerced to be in uppercase.  You can block this behavior on a field-by-field basis by defining the notUppercaseFields() function in your TabMenu’s user exit file to return an array of field names that should not be converted.

Example:

 

 

function notUppercaseFields() {

   return ['ELEM_ATT$AT_NAME']

}

 

 

 

You can also provide a “global” override for an entire TabMenu by setting the shouldNotAutoUppercase variable to true:

 

var shouldNotAutoUppercase = true;

 

How Can I Force the Save Button to be Enabled?

The save button usually synchronizes itself to the state of the model such that if it hasn’t been “dirtied” the button is disabled.  You may wish to control the state of the save button e.g. because a save should always/never be allowed.

Simply define the function saveButtonEnablingOverride() on your TabMenu user exit file to return a boolean indicating whether the save button should be enabled.  You can simply return a literal boolean, or perform any desired processing to determine the return value.

Example:

 

function saveButtonEnablingOverride() {

  return false;

}

 

How Can I Override the Processing After a Change/Add?

If you need to intervene in the processing after the system successfully completes a Change or Add operation, define the function privatePostChangeSucceeded() or privatePostAddSucceeded() in your TabMenu user exit file.  The function should return a boolean to indicate whether the system should refresh the UI with the newly returned server data.  You’d want to return false if e.g. you navigate to a different TabMenu.

Example :

function privatePostAddSucceeded() {

    var model = parent.model;

    var modeFlag = model.getValue('COMPL_NAV_MODE_FLG');

    var navKey = model.getValue('COMPL_NAV_KEY');

    var complSw = model.getValue('CMPLT_CLICKED_SW');

    if (complSw && model.getValue('ENRL_STATUS_FLG') == '30') {

        if (modeFlg && navKey){

             if (modeFlag == 'G') {

                parent.tabPage.gotoContext(navKey);

                return false;

             } else if(modeFlag == 'A') {

                parent.tabPage.addContext(navKey);

                return false;

             }

        }

   }

   return true;

}

 

How Do I Prevent the System from Setting Focus to a Widget after an Error?

When a service receives an error and shows a message after calling a back-end service, the browser attempts to set focus to the relevant widget in error.  If you don’t need this behavior, you can define the TabMenu variable dontSetFocusOnError to boolean “true.

Example:

var dontSetFocusOnError = true;

How Do I Prevent Attributes From Being Copied Into New List Elements?

The system automatically copies key fields (based on name matching) from a parent list element into new child elements (e.g. created by using the scroll ‘+’ button), in order to keep their prime keys consistent.  If you want to inhibit this operation for certain fields, define the TabMenu function dontCopyKeyNames_<LIST NAME> to return an array of fields that should not be copied into new elements of the list named LIST_NAME

Example:

function dontCopyKeyNames_ENRL_FLD() {

   return ['SEQ_NUM']

}

 

How Do I Customize New List Elements?

When you use ‘+’ button on a grid or scroll you get a new, empty list element.  If you want to customize the object, define a function in the TabMenu’s user exit file named initializeNewElement_<LIST_NAME>(newElement).

Example:

function initializeNewElement_ENRL_LOG(newElement) {

    newElement.set(‘ENRL_LOG_TYPE_FLG’, ‘USER’);

    newElement.set(‘USER_INFO’, parent.model.getValue(‘CURRENT_USER_INFO’));

}

How can I get my sequence numbers to default in an appropriate and consistent manner on my List Grid?

If you are working with a List Grid that uses some type of sequence field (e.g. SEQNO, LINE_SEQ, SORT_SEQ) , there is a handy bit of technology that you can use that will cause the UI to do this job for you. 

Just follow the steps below and you'll have the problem solved in no time. The sequence field will be populated in your "empty line" and any elements that are added from then on will have an appropriate value in the sequence field. If the user edits the sequence field at any point, the next element added to the list will incorporate the change without any problems.

Note.  The default Sequence Number functionality will default the next nearest tens value from the highest sequence. The defaulting will do nothing after the sequence reaches the highest number it can hold.

·         1)  In the user exit file of the Tab Menu - not the main Page or the List Grid - copy this JavaScript code:

function initializeNewElement_LIST_NAME(newElement) {

      var myListName = "LIST_NAME";

      var myListSeqName = "FIELD_NAME";

      var myListMaxSeq = 999;

      defaultSequenceNumber(myListName,myListSeqName,myListMaxSeq,newElement)

}     

 

</SCRIPT>

<SCRIPT src="/zz/defaultSequenceNumber/defaultSequenceNumber.js"></SCRIPT>

<SCRIPT>

 

·         For LIST_NAME, substitute your List Grid's list name. Be careful not to lose that underscore [ _ ] just in front of LIST_NAME in the first line! Remember that JavaScript is case-sensitive and make sure that you use all UPPERCASE letters as shown here.

·         For FIELD_NAME, substitute the name of your sequence field, whatever that might be in your List. Don't lose the quotes [ " ] ! Again, use all UPPERCASE letters.

How Do I Override the Tab Page Shown After An Error in a List (Grid/Scroll)?

When the system receives an error (e.g. after a Save) it attempts to set focus on the relevant widget, which might require flipping to a different tab page.  If the error relates to a list (grid or scroll) the system might not choose the tab page you’d prefer.  In that event you can control the tab page that should be opened by defining the TabMenu function overrideErrorTabPage_<LIST_NAME>().

Example:

function overrideErrorTabPage_BPA() {

    return 'bussProcessAssistantStepPage';

}

 

How Do I Disregard Unwanted Criteria From a Search Triggered Search by a Search Button?

When a search button (currently implemented as an IMG) is pushed, the system launches a search and “pulls” all applicable criteria values from the current model.  It might be that certain criteria fields should be ignored in a particular case.  You can define the function addIgnoreFieldsFor_<triggerFieldName>() on a tab or search page’s user exit file to specify fields to ignore whenever the IMG button named triggerFieldName is pushed on that page.

The function takes a single argument, fields, and you should add key/value pairs where the key is a field name to ignore, and the value is true.

Example:

addIgnoreFieldsFor_ADDRESS1_SRCH = function(fields) {

  fields['CITY_SRCH'] = true

}

 

addIgnoreFieldsFor_PER_ID = function(fields) {

  fields['ENTITY_NAME_SRCH'] = true

}

 

How Do I Disregard Unwanted Search Result Columns?

When you accept the result of a NOLOAD search the system tries to populate the selected search result row into the current model.  Sometimes this doesn’t make sense e.g. because there is no corresponding attribute for a display-only column.  You can exclude a column from being returned as part of a search result by defining the search client’s (Tab Page or Search window) function ignoreResultColumns() in the corresponding page’s user exit file.  Return an object with keys specifying attributes and values all set to boolean “true”.

Example:

function ignoreResultColumns() {

    return { ADDRESS1: true, CITY: true, POSTAL: true };

}

 

Since Searches can be shared by many search clients, it is possible that some clients want to get a specific column, but others don’t.  In that case, define the TabMenu function ignoreResultColumnsFor_<service> as above.

Example:

function ignoreResultColumnsFor_CILCCOPS() {

    return {CONT_OPT_TYPE_CD: true}

}

 

How do I format a value based on a given format?

If you need to format a value based on a given format, for example, on Person ID Number, if you select ID Type as SSN (999-99-9999), you can always format the Person ID Number before committing it to the server.

To do so, you can call the formatValue javascript function.

·         In the user exit file of the tab page include the following lines:

 

</SCRIPT>

<SCRIPT src="/zz/formatValue/formatValue.js"></SCRIPT>

<SCRIPT>

 

·         Now, you can start using the function to format a value. To use this function, you need to pass in both the value and the format into the function.

var phFormat = myData.getValue(pureListName + ‘PHONE_TYPE_FORMAT’);

if (pureFieldName == ‘PHONE’) {

  updateField(pureListName + ‘PHONE’ ,

        formatValue(myData.getValue(pureListName + ‘PHONE’), phFormat));

}

 

Hooking into Java User Exits (interceptors)

Create a class implementing any of the following Interceptor Java Interfaces whenever processing is required before or after the invocation of a service.  The CMServiceConfig.xml contains the mapping between services and corresponding classes that implement pre/post processing plug-ins.  The files should reside in the same directory as the service xml files, that is, in the <classpath>/services folder.  This can be arranged by placing the files in the web application server’s WEB-INF/classes/services folder, or placing them in an existing jar file.

Note: CM interceptors defined on the CMServiceConfig.xml override base product interceptors on the same service and action.

To implement an interceptor:

·         Creating a class implementing any of the Interceptor Java Interfaces.

·         Register the class in CMServiceConfig.xml.

Example

The following is a sample interceptor and configuration file, where one interceptor class implements all four interfaces.

Configuration file CMServiceConfig.xml:

<ServiceInterceptors

    <Service name="CMLTBTCP">

        <Interceptor action="add">

            com.splwg.cis.interceptortest.InterceptorTest

        </Interceptor>

        <Interceptor action="change">

            com.splwg.cis.interceptortest.InterceptorTest

        </Interceptor>

        <Interceptor action="delete">

            com.splwg.cis.interceptortest.InterceptorTest

        </Interceptor>

        <Interceptor action="read">

            com.splwg.cis.interceptortest.InterceptorTest

        </Interceptor>

    </Service>

</ServiceInterceptors>

 

Class com.splwg.cm.interceptortest.InterceptorTest:

package com.splwg.cm.interceptortest;

 

import com.splwg.base.api.serviceinterception.IAddInterceptor;

import com.splwg.base.api.serviceinterception.IChangeInterceptor;

import com.splwg.base.api.serviceinterception.IDeleteInterceptor;

import com.splwg.base.api.serviceinterception.IReadInterceptor;

import com.splwg.base.api.service.PageBody;

import com.splwg.base.api.service.PageHeader;

import com.splwg.base.api.service.RequestContext;

 

public class InterceptorTest implements IAddInterceptor, IChangeInterceptor,

        IDeleteInterceptor, IReadInterceptor {

 

    public PageBody aboutToAdd(RequestContext context, PageBody in) {

        System.out.println("aboutToAdd: " + in);

        return null;

    }

 

    public void afterAdd(RequestContext context, PageBody added) {

        System.out.println("afterAdd: " + added);

    };

 

    public PageBody aboutToChange(RequestContext context, PageBody in) {

        System.out.println("aboutToChange: " + in);

        return null;

    };

 

    public void afterChange(RequestContext context, PageBody changed) {

        System.out.println("afterChange: " + changed);

    };

 

    public boolean aboutToDelete(RequestContext context, PageBody in) {

        System.out.println("aboutToDelete: " + in);

        return true;

    }

 

    public void afterDelete(RequestContext context, PageBody in) {

        System.out.println("afterDelete: " + in);

    }

 

    public PageBody aboutToRead(RequestContext context, PageHeader in) {

        System.out.println("aboutToRead: " + in);

        return null;

    }

 

    public void afterRead(RequestContext context, PageBody result) {

        System.out.println("afterRead: " + result);

    }

}

 

Maintaining General-Purpose Maintenances

 

While most page maintenance classes are actually Entity-based (see Maintaining MO’s, below), it is sometimes necessary to write a general-purpose maintenance class for some specific purpose.

To develop such a Page Maintenance class, you need to create a hand-coded implementation class. This class subclasses a class (to be generated) with the same name (and package) as your class, but with the suffix "_Gen". For example, if your class is named SamplePageMaintenance, you'll subclass SamplePageMaintenance_Gen. Your class must include an annotation providing the metadata that describes the maintenance. This annotation is essentially a subset of the annotation for an entity page maintenance (aka MO maintenance), but leaves out details specific to the entity model (also known as the domain model). For example, the RowField annotation is not supported, since it links directly to an entity.

Here is an example of a simple PageMaintenance annotation:

 

@PageMaintenance (secured = false, service = CILABCDE,

      body = @DataElement (contents = { @DataField (name = DATA_FIELD1, overrideFieldName = FLD_NAME)

                  , @DataField (DATA_VALUE1)}),

      actions = { "read"},

      header = { @DataField (name = INPUT_FIELD1, overrideFieldName = FLD_NAME)

            , @DataField (name = INPUT_VALUE1)},

      headerFields = { @DataField (name = CONTEXT_NAME1, overrideFieldName = FLD_NAME)

            , @DataField (name = CONTEXT_VALUE1, overrideFieldName = FK_VALUE1)

            , @DataField (name = CONTEXT_NAME2, overrideFieldName = FLD_NAME)

            , @DataField (name = CONTEXT_VALUE10, overrideFieldName = FK_VALUE1)},

      modules = { "foundation"})

 

This example doesn't use any lists, but they are described and supported just like for entity maintenances.. By definition, any lists here are unmapped--that is, they are not populated by the framework from the entity model.

The supported actions are read, change, add, and delete. You can leave out the actions annotation completely if you intend to support all four of these actions. Otherwise, it's useful to declare what methods you'll support, so the framework can create an appropriate error message when an unsupported method is invoked.

You must implement one or more of the following action methods.

protected DataElement read(PageHeader header)

protected void change(DataElement item)

protected PageHeader add(DataElement item)

protected PageHeader copy(PageHeader header)

protected void delete(DataElement item)

 

The body of the implementation is completely up to you. The available API is largely the same as for entity page maintenance, e.g. you have a current session/transaction in which to execute queries, can access the entity model, etc. You are expected to throw ApplicationError or ApplicationWarning Java exceptions, as appropriate (e.g. via addError() and addWarning()), unless a serious or unforeseen problem occurs, in which case you should throw a LoggedException, or simply let the underlying Java runtime exception "bubble up".

The usual implementation of the read method would be to retrieve one or more parameters from the input page header, and construct a DataElement holding the desired return values, including any lists (which may be recursive).

For the change method, the usual behavior would be to examine the provided DataElement object, perform some operation, and return a different data element to hold the "changed" values.

The add method is similar to change in that it accepts an input DataElement, and returns the "newly added" DataElement instance (which should be a different instance than the input).

The delete method accepts a DataElement, but returns nothing after the conclusion of the operation.

Maintaining MOs

 

Contents

Maintaining Maintenance Classes for MOs

Maintaining Maintenance Objects

Maintaining Maintenance Classes for MOs

For a new MO, use the Eclipse plugin to create the skeletal class structure for a new maintenance object class.

For other services not linked to a MO, you will need to write a new maintenance subclass and create the annotation.

To develop an Entity Page Maintenance class, you need to create a hand-coded implementation class. This class must include an annotation providing the metadata that describes the maintenance. In addition, the business entities that "back" the maintenance must already have been created.

Let's take a look at a simple maintenance annotation example to illustrate its properties:

@EntityPageMaintenance (

      program = CIPTBTCP,

      service = CILTBTCP,

      entity = batchControl,

      copy = true,

      body = @DataElement (

          contents = {@DataField (DEFAULT_FOR_FLG)

                                , @RowField (name=foo, entity=batchControl)

                                , @ListField(name=BCP, owner=foo, property = parameters)

                           }

             ),

      lists = {@List (name = BCP, size = 50, copy = true,

                               program=CIPTBCPL, constantName="CI-CONST-CT-MAX-FIFTY-COLL",

                               body = @DataElement (contents = {@RowField (batchControlParameter)}))

              }

     )

First we see that this tag is an EntityPageMaintenance, meaning it is a page maintenance for a single entity root object. Here it is a batch control, but account, person, premise, etc. would also be examples. The idea here is that, by default, the maintenance framework tries to read, save, and delete the tree of data that starts with an instance of batch control. (Another kind of page maintenance is EntityListPageMaintenance, where you maintain a list of entities without a single root object. It has slightly different attributes than those discussed below.)

Next we list some attributes of the top-level annotation. The required program attribute gives the equivalent COBOL page module name that we're replacing. That's going to become important later when we create COBOL stubs to call back into Java from COBOL code that wants to call this maintenance, which will no longer be available as a COBOL implementation.

The service is the name of the page service that we are implementing. This is required so the framework knows that it should route requests for this service directly to this java class rather than invoking Jolt/Tuxedo/COBOL.

The required entity property names the entity that this maintenance uses for its root. It should match an entity that is defined within the system, else the maintenance obviously can't work!

The copy attribute signals that certain copy fields should be defined in the service. Making the framework explicitly aware of these fields is preferable to "dumb" coding of these fields.

Now we hit the two major structural elements, the body and lists attributes.

The body attribute always resolves to a DataElement, which is simply a way to organize the collection of "rows", "loose fields", and lists that belong to a particular level in the service data structure. These contents are simply held in the contents array, which here starts with a simple datafield, DEFAULT_FOR_FLG. Note that you simply reference the field name, and the generator uses the field metadata to infer its type and size. The next element of the contents array in this example is RowField. This is essentially a way of naming a reference to the properties of a single entity/table, including its language fields, if appropriate. You need to specify its entity and name. Here I use a dummy name, "foo". Finally we have a ListField, which consists of a reference to a list structure that is defined in a separate tag (lists). Here we merely name the referenced list by name, provide the owner which matches the name of the "parent" row, and the property, which tells the system how to access the list from the parent. Here we deduce that the getParameters() method on a batch control will yield the desired child list.

Contents

List Maintenances

Maintenance List Filters

List Maintenance Get More

List Maintenances

Writing a new list maintenance class requires you to create a new class, that provides an annotation with metadata, and lets you implement any user exits you need.

The class should subclass a generated class with the same name, but with suffix _Gen.

The annotation is @ListService, and is the same annotation structure that is used for lists within PageMaintenance. The service name should end with "L".

If there are child lists, you need to declare them with the lists annotation, just like for PageMaintenance.

You should specify any query criteria (from clause, where clause, order by clause) in the List Service annotation, see MaintenanceListFilter on how to implement that.

The default test superclass simply tests that at least one result row is returned.

Here is an example for testing this list maintenance:

package com.splwg.base.domain.batch.batchRun;

 

import com.splwg.base.api.service.ListHeader;

import com.splwg.base.api.testers.ListServiceTestCase;

 

import java.math.BigInteger;

 

public class ThreadListInquiry_Test

    extends ListServiceTestCase {

 
    //~ Methods ----------------------------------------------------------------------------------------------
 

    public String getServiceName() {

        return "CILTBTHL";

    }

 

    /**

     * @see com.splwg.base.api.testers.ListServiceTestCase#getReadListHeader()

     */

    protected ListHeader getReadListHeader() {

        ListHeader header = new ListHeader();

        header.put("BATCH_CD", "TD-BTERR");

        header.put("BATCH_NBR", BigInteger.valueOf(1));

        header.put("BATCH_RERUN_NBR", BigInteger.valueOf(0));

 

        return header;

    }

}

Maintenance List Filters

A given list on a maintenance may not need to return all the data in the list. Instead, a filter can be applied to return a subset of the data. In COBOL this was accomplished easily enough, as the developer always wrote the SQL to retrieve the list. In Java, you get the main list by default. And now, you can modify the SQL that will be used for the list retrieval by writing HQL to filter the list. This HQL goes into the @List annotation's "fromClause" and "whereClause" properties. This is written as an HQL filter HQL, where the main table (and its language alias, if one exists) already "exists" in the background, and can be referenced by the alias "this" (and "thisLang" for the language row). New entities can be added to the ‘from’ clause, and a ‘where’ clause can be specified. A select clause should not be specified (instead results can be added in the bindList user exit - see below), and neither should an order by (the order by is specified separately).

Additionally, if there are extra values that can be retrieved via a join, the loose data fields can be specified as a @ListDataField, with an hqlPath property specifying the hql path to select the result. And finally, you can bind parameters and also specify extra results into the query in the bindList user exit specific for the given list.

List Maintenance Get More

When a list is too large to send to the UI in one shot, there is the ability to "get more" rows. In COBOL, this was done explicitly through the developer writing the SQL to retrieve all the rows, after some given input. This depended on the retrieved order, and could be buggy and complex when multiple fields were used in the order by.

In the Java case, a @List can simply be told its order by (separate from any other Maintenance List Filter properties). Everything else is automated by the FW. There is no need to add special LAST_ fields to the annotation/service, nor even to add the parent's PKs.

The order of a maintenance's list can be given in two ways:

·         The order can come from the domain _Impl class. However, this is limited as it may only use fields on the entity table itself (not even language properties).

·         The order can be specified on the @List annotation as a string "orderBy", written in hql form, using the special entity alias "this" to refer to the row (and "thisLang" to refer to the language row if one exists), and including any other aliases available from the fromClause property.

The list will retrieve rows in chunk sizes given by the size property on the @List annotation.

An example of using this filtering to join extra information is available on the class MaintenanceObjectMaintenance. Another example on a ListService, is available on the class NavigationOptionMenuList.

Besides using a MaintenanceListFilter and knowing how to deal with list get mores, lists in a page maintenance will automatically retrieve (and cache) the language row associated with the main row of the list. This helps the n+1 select problem (only a single SQL is issued, instead of the main one, plus an extra one for each of the rows' language row), and also provides the ability to have short hand for the orderBy property of a list. If the order is simply by a language property, then you can reference it by thisLang.property, without having to supply a filterClause.

Maintaining Maintenance Objects

A maintenance object is a group of tables maintained together within the system.

For detailed information about Maintenance Objects, please refer to user document Framework Administration, Database Tools, Defining Maintenance Object Options

Maintaining Database Meta-data

Contents

Maintaining Fields

Maintaining Tables

Maintaining Fields

Field represents a column on a database table.  A Field must exist before it can be defined on a Table.

For detailed information about fields, please refer to user document Framework Administration, Database Tools, Defining Field Options.

Maintaining Tables

Table represents a database table used to store data or a database view.

For detailed information about menus, please refer to user document Framework Administration, Database Tools, Defining Table Options.

Maintaining Java Classes

 

Contents

Maintaining Business Entities

Maintaining Business Components

Maintaining Maintenance Classes, including collections

Maintaining Business Entities

Contents

Business Entity Background

Persistent Classes

Creating the Implementation Class

Developing Change Handlers

Business Entity Background

A central framework concept is the Business Entity that allows for persistent data in the database to be interacted with as objects.  In general there is at least one Business Entity class corresponding to each table in the system. Likewise an instance of one of these classes corresponds to a row in the database. Here are some things to remember about Business Entities:

·         When you create a new instance of a Business Entity and the current transaction commits, a new row is committed to the database. Likewise when instances are deleted or changed, corresponding deletes or updates are performed on the database.  There is no concept of a transient entity in our architecture; application logic is dealing with only persistent objects.

·         When the actual insert, update and delete statements are issued to the database is controlled by the framework and may be deferred for performance reasons.  The framework is, however, expected to issue DML with sufficient timeliness to maintain data consistency so that application code need not concern itself with when statements are actually executed.

·         When you use the query language (HQL) the returned objects are Business Entities (or scalars in the case of "count" or other aggregate functions).  These objects may be modified by application code and those changes will be persisted to the database.

·         The way you change the properties on entities is via the Data Transfer Objects corresponding to the entity.

Contents

How do I create a new Business Entity instance?

How do I change values on an existing Business Entity instance?

How do I delete Business Entity instance?

How do I create a new Business Entity instance?

Creating a new entity is equivalent to inserting a new row into the database. The first thing you need is to have the framework create a new instance of the correct Data Transfer Object for you so that you can set properties for the new entity instance.  This can be done via one of the standard framework methods accessible from the abstract superclasses of classes holding business logic.  This method can be told which DTO to create by passing the business interface class for the entity.  In the example below, we are creating a new Person_DTO.

Person_DTO personDTO = (Person_DTO) createDTO(Person.class);

 

Alternatively, if you find that you have a reference to an Id class, the appropriate DTO can be created via a method generated onto that Id class.

Person_Id aPersonId = new Person_Id("123467890");

Person_DTO personDTO = aPersonId.newDTO();

 

Now let's set some values for the new Person instance:

personDTO.setStateId(state.getId());

personDTO.setLanguageId(language.getId());

 

Finally we try to create a persistent instance based on these values. This is equivalent to doing the insert against the underlying table except that: (1) required validation occurs and (2) the timing of actual insert occurs at the discretion of the framework.

Person person = (Person) createBusinessEntity(personDTO);

 

That's it. When the current transaction is committed, a new person will be added to the database.

How do I change values on an existing Business Entity instance?

There are really three steps:

·         Ask the existing entity for its DTO.

·         Change the appropriate values on the DTO.

·         Call setDTO() on the entity instance.

Person_DTO dto = person.getDTO();

dto.setAddress1("invalid value");

person.setDTO(dto);

 

Necessary Change Handlers will fire to validate the change to this “person” object as well as other cascading events as specified in the entity’s change handlers.

How do I delete Business Entity instance?

There are a number of ways to delete entities.

1.     Delete an instance that you have a reference to:

person.delete();

 

2.     Delete an instance where you have only its Id:

delete(personId);

 

3.     Delete the results of a query

Query query = createQuery("from Person person where exists ( "

    + " from PersonName as perName where person = perName.id.person and "

    + "perName.isPrimaryName = :systemBool and perName.entityName "

    + "like :name)");

 query.bindLikableString("name", "ABC", 64);

 query.bindBoolean("systemBool", com.splwg.base.api.datatypes.Bool.TRUE);

 

 long rowsDeleted = query.delete();

 
Persistent Classes

Behind the scenes, the persistence and validation mechanisms are quite complex and require the collaboration of many classes and pieces of configuration data.  Thankfully, most of this complexity is hidden from the application programmer.  Still, there are various classes that the application programmer will deal with:

·         Framework Classes that act as an application programming interface.  These API classes are directly referenced by application code.

·         Generated Classes that are created for each business entity that serve two purposes:

·         They provide convenient methods (like property “getters” and “setters”) based on the structure of the specific entity, it’s fields, child collections and key structure for example.

·         They are necessary for the persistence mechanisms to work correctly.

·         Handcoded classes that the application programmer is expected to write.  Many of the handcoded classes are read by the artifact generator so the framework can “wire up” the handcoded functionality.

Some examples of the above classes are shown below.

Creating the Implementation Class

There is very little that needs to be done by application developers to create a basic business entity. In addition to the setup of the CI_MD_* tables describing the entity and its constraints only an implementation class (or "Impl" for short) needs to be added.  In this case a developer added Person_Impl. The following is a simple example of an "Impl" class for the Person entity.

/**

 * @BusinessEntity

 *   (tableName = CI_PER,

 *    oneToManyCollections  = { @Child( collectionName = names,

 *                                 childTableName = CI_PER_NAME,

 *                                 orderByColumnNames = { SEQ_NUM } )

 *                            }

 *   )

 */

public class Person_Impl

    extends Person_Gen {

    /**

     * @return the UI Display "info" for this person

     */

    public String getInfo() {

        return "PrimaryName: " + getPrimaryName().getInfo();

    }

}

Important parts of the implementation class are described below:

·         The implementation class name must end with the suffix “_Impl”.  For example, if the entity has a name of “person” then the implementation class name of “some.package.Person_Impl”.  It also means that the generated business interface will have a name of  “some.package.Person”.

·         A Class Annotation which declares:

·         What table this entity represents

·         What the owned-child tables are and what they should be called

·         Other information. Please see the BusinessEntityAnnotation class for more details.

·         The class extends an abstract superclass having the suffix of “_Gen”.  Continuing the example of an entity named “person”, the implementation class would extend a not-yet-created abstract superclass named “some.package.Person_Gen”.  This superclass is created by the artifact generator based on metadata about the table and contains:

·         Getter methods for properties including parent objects and collections

·         The getDTO() and setDTO(...) methods that allow for properties to be changed

·         Access to standard framework methods like createQuery(...)

·         Business methods.  Any hand coded public methods are automatically exported onto the generated business interface (i.e. “some.package.Person”).  Client code can then access the added business method as follows:

 Person aPerson = some logic retrieving a person instance

 String thePersonsInfo = aPerson.getInfo();

 

·         Constants.  Any hand coded public static final variables are automatically exported onto the generated business interface. This will be useful for constants related to the entity.

Having created a new entity, it is likely that validation rules and other behaviors should be added to it.  Please see Adding Change Handlers for more information.

Developing Change Handlers

The creation of Business Entities allows business logic to interact with rows in database tables as objects and in doing so allows business methods to be invoked on those objects to perform some business function.  Quite another thing is how the entities react to proposed changes in their state.  Outside callers have no business being exposed to the internal validations and cascading state changes within the objects that they interact with.  Because of object encapsulation, they should not be exposed to such issues.  Nonetheless, there needs to be a way to program the internal logic of entities.  This is the reason for Change Handlers, to provide for objects to react to proposed changes in their state.

Change Handlers are classes that add behavior to entities.  This behavior takes two forms.

·         Validation rules.  This allows for proposed changes to be validated against business rules.  These rules are expected to be “side effect free” meaning that the validation does not change the state of the system.  By calling side effect free validations only after all changes to entity state have been performed, the framework can avoid many complex scenarios where invalid data can “slip past” validations.

·         Cascading change logic.  This allows changes to this entity to cause changes to other entities.

Contents

Creating the Change Handler Class

Testing the Change Handler Class

Validation Rules

Creating the Change Handler Class

A Business Entity may have more than one Change Handler.  The framework will call each handler associated with an entity when an attempt is made to modify the state of the underlying entity.  The following are the important parts of a Change Handler class:

·         The class should extend the AbstractChangeHandler class and have a class name ending with “_CHandler”.

·         The @ChangeHandler class annotation.  This tells the framework which entity to attach the change handler to at runtime.

·         Implement any “handle” methods.  These are methods that can implement any cascading effects of the proposed change to the entity’s state.

·         Construct Validation Rules that are returned by public static methods on the change handler class.  There should be one static method per rule.  The reason for exposing these methods is to facilitate testing (see below).  Static methods are used instead of static variables to prevent timing problems associated with the static initialization of static variables.

·         Return an array of the rules created above via the getValidationRules() method.  The framework invokes this method at runtime to retrieve the rules.

·         Make sure to run the artifact generator and rebuild source code after adding a Change Handler or modifying its annotation.

Testing the Change Handler Class

When adding behavior to an entity, it is desirable to do the following:

·         Break the rules into modular pieces that can be independently maintained and tested.

·         Test that each behavior works by creating a JUnit test for each distinct behavior.

The following steps are recommended when adding new change handlers so that the additional behavior is sufficiently tested.

·         Add each rule to the change handler at once using instances of the PlaceHolderRule class.  Use an appropriate RuleId and Description as self-documentation of what the rule is supposed to do.

·         Add a new test class by extending AbstractEntityTestCase.  This class should reference the change handler being added and will insure that each rule is violated by at least one test.  The test class name should end with “Test”.

·         Run the test class as a JUnit test.  The test class should complain that there was at least one rule that was not violated by the test class.  For the rule that was not violated, add a test method to the test class and also add the “real” validation logic to the change handler class.  Try executing the test class again.  Continue implementing more test methods and rules until all rules are tested and the JUnit class completes successfully.  Below is an example, test method for a rule that tests both a successful change and an unsuccessful change.  It is important to insure that the validation error is thrown by the actual rule being tested.

public void testAddressOneLabelRequiredIfAddressOneIsAvailable() {

   //pass

   Country country = (Country) createQuery(

       "from Country country").firstRow();

   Country_DTO countryDto = country.getDTO();

   country.setDTO(countryDto);

 

   //fail

   countryDto.setAddress1Available(Bool.TRUE);

   countryDto.setLanguageAddress1("");

   try {

       country.setDTO(countryDto);

       fail("A validation error should have been thrown");

   } catch (ApplicationException e) {

     verifyViolatedRule(Country_Chandler

         .addressOneLabelRequiredIfAddressOneIsAvailable(), e);

   }

   }

 

·         Add other test methods to test “handle” methods on the change handler as well as business methods that may have been added.

Validation Rules

Validation rules are the mechanism for describing to the runtime system how it should validate business entities. There are a few important characteristics of these rules:

·         The coding style is declarative. That is, every attempt has been made so the programmer specifies what makes data valid, not how or when the validation should take place.

·         Only in the case of "custom rules" does the programmer need to build the step-by-step logic specifying how the validation should take place.

·         Validation rules are side-effect free. That is, they cannot change the persistent state of the system. This insures that all the validations are performed on the complete set of changes. Likewise, it allows for the startChange()/saveChanges() logic to safely defer the firing of rules until the end of the coherent set of changes.

Contents

The Rules

Custom Rules

Conditions

The Rules

A number of useful rules are provided in the interest that the application programmer can use them with a minimum of programming. These are classes that implement ValidationRule and can be used by application logic:

·         ProtectRule will protect one or more properties on an entity.

·         RequireRule will require that a property be populated.

·         AllowRule allows a value to be populated.

·         AllowAndRequireRule both allows and requires that a property be populated.

·         DecimalRule provides some common validations against decimal data types.

·         CustomRule will create a rule out of a CustomValidation class implementing logic that just cannot be handled by existing rules.

·         RestrictRule will restrict a property to a set of values

Each of the rules above provides standard rules that represent similarly configured rules that are used repeatedly in the system. These standard rules can be created via static "factory" methods on the rules themselves. Consider the following standard rule:

/**

 * Protect the dependant property when the primary property is equal to the supplied lookup value.

 *

 * @param ruleId a unique ruleId

 * @param description a description

 * @param primaryProperty the property that the condition depends on

 * @param dependantProperty the property that is protected when the condition is true

 * @param primaryLookupValue a {@link Lookup} value that the primary property must equal for the dependant property

 *                            to be protected

 * @return a new rule

 */

public static ProtectRule

   dependantPropertyWhenPrimaryMathesLookup

        (String ruleId,

         String description,              

         SingleValueProperty primaryProperty,

         SingleValueProperty dependantProperty,

         Lookup primaryLookupValue)

 

What this rule does is prevent one property from being changed (the "dependant" property) when another property (the "primary" property) matches a certain value. An example would be the "freeze date/time cannot be changed when the status is 'frozen'". In this case, the dependant property would be the freeze date/time and the primary property would be the status. The lookup value of "frozen" would be passed in as the lookup value.

Custom Rules

There are situations when custom rules need to be coded. These are for situations too complex for a declarative rule. The process is as follows:

·         Create a class that extends AbstractCustomValidation. Implement one or more of the abstract methods corresponding to various "events" that may occur with respect to the underlying entity.

·         Within any change handler requiring this rule, instantiate a CustomRule passing in the class created above.

For details on the "events" that can be processed by the custom validation, please refer to the JavaDocs.

When coding a CustomValidation that will be used by a CustomRule. It is important to understand when these events fire.

·         Eager Validations fire "immediately" when the underlying entity is changed (either via delete, setDTO(), or createEntity()).

·         Lazy Validations fire when a "coherent set of changes" is complete.

Conditions

The rules wouldn't be very useful if all you could do was always protect or require properties. This behavior is usually based on conditions. Rules take as input one or more Conditions (i.e. objects implementing the Condition interface). Right now, there are several conditions that can be used:

·         Equals. This condition can compare properties to each other or to constants (lookup values, Strings, etc). Likewise, the size of a collection can be compared using Equals (i.e. determine personNames' size equals 0 would mean there are no names for a person). Finally, null values can be tested using a special constant value "Constant.NULL".

·         Not. This is the basic boolean operator that can change the value of other conditions.

·         And. This is the basic boolean operator that takes two child condtions, and return true if each of them are true. This is evaluated "lazily" and won't even evaluate the second condition if the first is false (a performance enhancement).

·         Or. This is the basic boolean operator that takes two child conditions, and return true if either of them are true. This is evaluated "lazily" and won't even evaluate the second condition if the first is true (a performance enhancement).

·         GreaterThan / GreaterThanOrEquals. This evaluates whether one property/constant is greater than (or greater than or equal to) to another property/constant.

·         LessThan / LessThanOrEquals. This evaluates whether one property/constant is less than (or less than or equal to) to another property/constant.

·         Contains. These are conditions for a collection of children- at least one element has condition x, at most 2 elements match condition y, etc). The child condition's properties should be referenced from the point-of-view of the child row.

Each of these conditions is accessible from the corresponding property or condition. There should be no reason in normal development to use the constructors for the conditions above. Instead, you could say, for instance

Condition isPrimaryName = PersonName.properties.isPrimaryName.isTrue();

or

Condition isAlias

   = PersonName.properties.nameType.isEqualTo(NameTypeLookup.ALIAS);

or

   Condition greaterThan 
      = PersonName.properties.sequence.isGreaterThan(BigInteger.ZERO);

or

Condition hasOnePrimaryName

   = Person.properties.names.containsAtLeastOne(isPrimaryName);

or

Condition notAlias = isAlias.not();

Maintaining Business Components

Business Components are business objects having two important characteristics.

·         They are non-persistent holders of business logic. That is, they are the place to put business logic not tied to a single business entity instance (i.e. a single "Account" or "Person".) This makes them analogous to "common routines".

·         When allowed, implementations of business components may be replaced at runtime by custom classes implementing the same business interface. An example of this includes "info" logic.

Contents

Creating Business Components

Component Replacement

Calling Components

Creating Business Components

Much like Business Entities, it is necessary to create an implementation (*_Impl) class containing the actual logic that is then processed by the artifact generator. Below is an example that would be created by hand:

/**

 * Component used to query for {@link Person} instances based on various

 * predefined criteria.

 *

 * @BusinessComponent

 *   (customizationReplaceable = false)

 */

public class PersonFinders_Impl

    extends GenericBusinessComponent

    implements PersonFinders

    /**

     * @param   nameType  a name type

     * @return  count of names by name type

     *

     * @BusinessMethod (customizationCallable = true)

     */

    public int findCountByNameType(Lookup nameType) {

        Query query = createQuery

            ("FROM PersonName name where name.nameType = :type");

        query.bindLookup("type", nameType);

 

        return (int) query.listSize();

    }

}

This example shows a "finder" component that is responsible for holding queries related to the "person" entity. These queries are not related to any particular person because, in that case, they would rightfully belong on the entity implementation class itself. Our (cooked up) example shows a single method that returns a count of PersonName instances by name type.

Let's look at various parts of the component:

·         @BusinessComponent class annotation.

·         customizationReplaceable attribute specifies whether or not customers can replace this component at runtime. The default is false. If a component is "replaceable", its methods are assumed to be "customizationCallable".

·         GenericBusinessComponent is extended which gives this class access to framework methods.

·         PersonFinders is implemented. This is the name of the generated business interface. Any customized replacement of the business component would implement this interface as well.

·         The business method findCountByNameType. For the method to be exported to the business interface (and therefore callable by other business objects), it must be public.

·         @BusinessMethod is an optional method-level annotation.

·         customizationCallable specifies that this method is part of the "supported" API. That is, our customers are entitled to call this method from their customizations and therefore, we must change this method with great reluctance in future release.

Component Replacement

Business Components provide a simple extension mechanism where base-package code can be made available to be replaced by customizations.  For this to take place, two things must take place:

·         A component is added as described above with the customizationReplaceable annotation attribute set to true.

·         A replacement component is created that implements the business interface of the original component and also sets the replacementComponent attribute to true.

An example, replacement of the PersonFinders component is shown below. Component implementations are registered in the same order as the "application stack", that is "base" followed by "ccb" then followed by “cm”. After the component is defined in one application, derived applications (higher on the stack) can replace the implementation.

package com.abcutilities.cis.customizations.person;

/**

 * @BusinessComponent

 *   (replacementComponent = true)

 */

public class CustomizedPersonFindersImpl

    extends GenericBusinessComponent

    implements PersonFinders {

 

    public Integer findCountByNameType(PerOrBusLookup nameType) {

        ... customized code ...

    }

    ...

}

Calling Components

Business Components are accessed via their business interfaces. Following is an example of how to call the above component from some other business object: 

PersonFinders finders = PersonFinders.Factory.newInstance();

int count = finders.findCountByNameType(NameTypeLookup.constants.PRIMARY);

logger.info(count + " primary names found");

Maintaining Maintenance Classes, including collections

Maintaining Services

This defines services available in the system.  These include user interface services as well as stand-alone XAI services.  Use this transaction to introduce a new user interface or stand-alone XAI service.

For detailed information about service programs, please refer to user document Framework Administration, XML Application Integration, Setting Up Your XAI Environment, Setting Up Your Registry, Service Program.

Maintaining Foreign Key References

You need to setup foreign key references if you have characteristics whose valid values are defined in another table (i.e., you use “foreign key reference” characteristic types).

For detailed information about foreign keys, please refer to user document Framework Administration, Defining General Options, Setting Up Foreign Key Reference Information.

Maintaining Lookup Tables

Some special fields are defined as “lookups” in the system.  These fields have a predefined set of values for which language-dependent descriptions are supplied to be displayed in the online system.

For detailed information about lookups, please refer to user document Framework Administration, Database Tools, Defining Look Up Options.

Maintaining Navigation Keys

Each location to which a user can navigate (e.g., transactions, tab pages, tab menus, online help links, etc.) is identified by a navigation key.  A navigation key is a logical identifier for a URL.   

For detailed information about navigation keys, please refer to user document Framework Administration, User Interface Tools, Defining Navigation Keys.

Maintaining Navigation Options

Every time a user navigates to a transaction, the system retrieves a navigation option to determine which transaction should open.  Many navigation options are shipped with the base package and cannot be modified as these options support core functionality, but you may need to add additional navigation options to support your specific business processes.

For detailed information about navigation options, please refer to user document Framework Administration, User Interface Tools, Defining Navigation Options.

Maintaining User Interfaces

The configuration tools allow you to extend the front-end user interface.  The main component of this is a UI Map, supported by Business Objects and Business Services.

For detailed information about user interfaces, please refer to user document Framework Administration, Configuration Tools.

Maintaining Menus

This metadata represents the root of a menu “tree”.  A menu contains a list of menu “lines”, which, in turn, contains a list of menu “items”.  Lines can define navigation keys and/or associated actions, or further submenus.

For detailed information about menus, please refer to user document Framework Administration, User Interface Tools, Defining Menu Options.

Maintaining Application Security

Application security defines how a particular application service is used, namely:

·         Which user groups can access the service

·         What actions may be performed within the service

For detailed information on how to define application security, please refer to user document Framework Administration, Defining Security & User Options.

Maintaining UI Components (Translation)

You can use the override fields on some of the system data tables to modify and customize the labels, buttons, titles, tab names and messages on the standard user interface.  This may be helpful to correct minor interface inconsistencies and inappropriate translations as well as to provide translations for any single fixes that you may have applied to your environment.  (Single fixes release without translation, so you may need to translate any labels and descriptions for new UI components or messages.) 

You can manually modify the descriptions or translations of the following items:

·         Dialog titles

·         Transaction titles and tab labels

·         Field labels on tab pages

·         Button labels

·         Messages

Contents

Flushing Server and Client Caches

User Language

Modifying Dialog Titles

Modifying Transaction Titles and Tab Labels

Modifying Field Labels on Pages

Modifying Button Labels

Modifying Messages

Flushing Server and Client Caches

A great deal of information in the user interface changes infrequently, including field labels, menu items, and drop down lists.  In order to avoid accessing the database every time this type of information is required by an end-user, the system maintains a cache of static information on the web server.  Additionally, depending on how you set up the preferences on your Web browser, these items may also be cached in the browser.

After you make a change to a user interface item, such as a field label, you may need to flush the appropriate cache on the Web server as well as the client.

For information about flushing caches on the Web server, refer to the Caching Overview section in the Defining General Options chapter of the Oracle Utilities Application Framework Administration documentation.

User Language

You must log in as a user ID that has the same language as the items for which you want to modify the description.  For example, if you want to modify a French message, you must log in with a user ID that is set to use French.  The instructions in the following sections assume that you are logged in with a user ID that has the appropriate language set.

Modifying Dialog Titles

A dialog can be a search window or dialogs that provide additional functionality, such as the Start / Stop Confirmation Dialog or the Generate Bill dialog.

Dialog Title

To modify a dialog title:

·         Navigate to and open the dialog with the title that you want to change. 

·         Right-click near the top of the dialog and select View Source from the pop-up menu.

View Dialog Source

Note.  Many dialogs and windows have multiple source files; so if you can’t locate the field you are looking for, try right clicking in a different area (closer to the label you want to modify).  For example, if you right-click in the grid area of the Person Search illustrated above, you will open a different source file.  If you already know the name of the field you want to modify, you can skip this step.

·         In the displayed source file, locate the field name that has the value you want to modify.  The field for the dialog title is clearly labelled and the current value of the field is displayed after the hyphen.

Title Field Name

·         To modify the field override via the application, navigate to Admin Menu - Database – Field in the Oracle Utilities Application Framework application.

·         When the field search dialog appears, enter the name of the field as it appears in the source.

·         Enter an Override Label with a title description to suit your needs and save your changes.

Database - Field

·         Flush the server and browser caches and verify that the new dialog title appears correctly.

Modifying Transaction Titles and Tab Labels

You can modify the transaction title and or the tab labels that appear on a transaction.

Transaction Title and Tab Labels

To modify the transaction title and/or tab labels:

·         Navigate to the transaction that has the title and/or tab name you want to modify.

·         Right-click in the empty area to the right or left of the tab bar and select View Source from the drop-down menu.

View Transaction Title/Tab Source

Note.  Many dialogs and windows have multiple source files; so if you can’t locate the field you are looking for, try right-clicking in a different area (closer to the label you want to modify).  To view the source for the transaction title and tab bar, right-click directly to the right or left of the tab bar.  If you already know the name of the field you want to modify, you can skip this step.

·         In the displayed source file, locate the field name that has the value you want to modify.  The fields for the transaction titles and tab labels are clearly labelled and the current values of the fields are displayed after the hyphens.

Transaction Title and Tab Field Names

Subsystem Name.  If you modify the subsystem field description, your changes will appear on every transaction that is part of the subsystem.

·         To modify the field override via the application, navigate to Admin Menu - Database – Field in the Oracle Utilities Application Framework application.

·         When the field search dialog appears, enter the name of the field as it appears in the source.

·         Enter an Override Label with a title or tab description to suit your needs and save your changes.

Database - Field

·         Flush the server and browser caches and verify that the new field label appears correctly.

Modifying Field Labels on Pages

You can modify field labels that appear on transactions.

Field Labels

Field labels may be reused!  A field label may be reused on multiple transactions and tabs.  If you override the field’s label, your changes affect all pages and transactions on which that field label appears.

To modify the field labels that appear on transactions:

·         Navigate to the transaction that has the field name you want to modify.

·         Right-click in an empty area near the label and select View Source from the drop-down menu.

View Page Source

Note.  Many dialogs and windows have multiple source files; so if you can’t locate the field you are looking for, try right-clicking in a different area (closer to the label you want to modify).  If you already know the name of the field you want to modify, you can skip this step.

·         In the displayed source file, locate the field name that has the value you want to modify.  The fields for the labels are clearly identified and the current values of the fields are displayed after the hyphens.

         

Field Label Names and Values

Table-specific Fields.  Note that some labels may be specific to the table on which they appear, while other labels are generic throughout the application.  If a field label is specific to a table, the table name appears before the $ in the field list.

·         If the label is table-specific, navigate to Admin Menu - Database - Table in the Oracle Utilities Application Framework application and search for the name of the table.

Search for Table

·         Navigate to the Table Field tab and scroll to the field whose label you wish to modify.

Table Field

·         Enter an Override Label to suit your needs and save your changes.

·         If the label is not table-specific, navigate to Admin Menu - Database - Field and search for the field name.

·         When the field appears, enter an Override Label to suit your needs and save your changes.

Database - Field

·         Flush the server and browser caches and verify that the new field label appears correctly.

Modifying Button Labels

Button labels are just like field labels; they are stored in the field table.  You can modify button labels just like you can field labels.

Button Labels

To modify button labels:

·         Navigate to the transaction that has the button label you want to modify.

·         Right-click in an empty area near the label and select View Source from the drop-down menu.

View Page Source

Note.  Many dialogs and windows have multiple source files; so if you can’t locate the field you are looking for, try right clicking in a different area (closer to the label you want to modify).  If you already know the name of the field you want to modify, you can skip this step.

·         In the displayed source file, locate the field name that has the value you want to modify.  The fields for the labels are clearly identified and the current values of the fields are displayed after the hyphens.

Field Label Names and Values

·         Navigate to Admin Menu - Database - Field in the Oracle Utilities Application Framework application and search for the field name.

·         When the field appears, enter an Override Label to suit your needs and save your changes.

Database - Field

·         Flush the server and browser caches and verify that the new field label appears correctly.

Modifying Messages

You can modify the message text and description for messages, such as error, warning and validation messages.  The following example shows a validation message: 

Message

To edit messages, you need to know the message category and number.  The category is the part of the message number that appears before the comma.  In the example message above, the category is 3.  The number is the part of the message number that appears after the comma.  In the example message above, the message number is 253.

To edit the message text or description:

·         Navigate to Admin Menu - System - Message.

·         Specify the message category in the search dialog.

·         Specify the starting message number and click the search icon.

System - Message

·         Click the go to button for the message you want to edit.  You are transferred to the Details tab for that message.

Message Details

·         Enter the customer specific message text and description as appropriate for your needs.

Message Variables.  Messages may have one or more variables.  Variables are indicated by a percent sign (%) followed by a number.  A value is substituted for the variable before the message is displayed.  Do not modify the message variables and make sure that your custom message contains the same number of variables as the original.

·         Save your changes.

If possible, you can attempt to verify that the message was changed correctly.  However, it is not always easy to determine and duplicate the situations where a specific message may appear.

For more information about system messages, please refer to user document Framework Administration, User Interface Tools, Defining System Messages.

Plugging in Algorithms

The following will illustrate the steps to create a new plug-in algorithm.  This example will create a new Adhoc characteristic validation algorithm that is very similar to a delivered plug-in. 

Contents

Creating Algorithm Spot Implementation Class

Add Algorithm Type

Add Algorithm

Create References to New Algorithm

Creating Algorithm Spot Implementation Class

Contents

Review Algorithm Spot Definition

Create Algorithm Component Implementation

Review Algorithm Spot Definition

The algorithm spot definition identifies the purpose of the algorithm spot and the required methods per implementation.   It may also help to look at existing implementations of the relevant algorithm spot.

The relevant algorithm spot in this example is AdhocCharacteristicValueValidationAlgorithmSpot in com.splwg.base.domain.common.characteristicType.

Create Algorithm Component Implementation

Copy the existing numeric validation plug-in "AdhocDateValidationAlgComp_Impl and name it as " AdhocDateAgeValidationiiiAlgComp_Impl" where iii is your initials.

Modify the annotation to replace the last Date Format soft parameter with two decimal parameters (ageFrom and ageTo).  

In addition, modify the validateDateInRange method to check that the age (given date less the system's current date / 365.25) will be greater than the soft parameter ageFrom (if non-zero), and will be less than the ageTo (if non-zero).  Make sure that negative numbers are allowed so that this plug-in can be used to compare against some future "expiration date" kind of scenarios.

Generate and build the java classes.

The various "Adhoc characteristic value validation" algorithms that come with the Oracle Utilities Software Development Kit are good references for algorithm plug-ins.

Add Algorithm Type

Add a new algorithm type copying most of the entries for ADHV-DTD:

·         Algorithm Type: CM ADHV-iiiJ where iii is your initials.

·         Description: Validate Date Field (Age) 

·         Long Description: <Copy ADHV-DTD description here>.  The Parameters From Age and To Age are optional decimals.  The algorithm will check the "age" (current system date less the characteristic date / 365.25) is not less than the From Age (if non-zero) and is not more than To Age (if non-zero).

·         Algorithm Entity: Char Type - Adhoc Value Validation

·         Program Type: Java

·         Program name: com.splwg.cm.domain.common.characteristicType.AdhocDateAgeValidationiiiAlgComp where iii is your initials.

·         Parameters:

·         Sequence: 1, Parameter: From Date, Required: Not Checked

·         Sequence: 2, Parameter: To Date, Required: Not Checked

·         Sequence: 3, Parameter: Date Format1 (Stored Format), Required: Checked

·         Sequence: 4, Parameter: Date Format2, Required: Not Checked

·         Sequence: 5, Parameter: Date Format3, Required: Not Checked

·         Sequence: 6, Parameter: Date Format4, Required: Not Checked

·         Sequence: 7, Parameter: Date Format5, Required: Not Checked

·         Sequence: 8, Parameter: Age From, Required: Not Checked

·         Sequence: 9, Parameter: Age To, Required: Not Checked

Algorithm Type

Add Algorithm

Add a new algorithm as follows:

·         Algorithm: CM EXPDT-iii.

·         Description: Date must be a future date

·         Algorithm Type: CM ADHV-iiiJ

·         Effective Date: 1/1/2005

·         Parameters:

·         Sequence: 1, Parameter: blank

·         Sequence: 2, Parameter: blank

·         Sequence: 3, Parameter: YYYY-MM-DD

·         Sequence: 4, Parameter: YYYY/MM/DD

·         Sequence: 5, Parameter: MM-DD-YYYY

·         Sequence: 6, Parameter: MM/DD/YYYY

·         Sequence: 7, Parameter: MM.DD.YYYY

·         Sequence: 8, Parameter: 0.001

·         Sequence: 9, Parameter: 0

Algorithm

Create References to New Algorithm

Create an ad hoc characteristic type and reference the previously created algorithm on it.

·         Char type: CM J-iii

·         Description: iii’s Adhoc validation test / Expiration Date

·         Type of Characteristic Value:  Ad hoc Value

·         Validation rule: CM EXPDT-iii

·         Allow Search by Char Val:  Not Allowed

Characteristic entity: choose Notification Upload Staging.

Characteristic Type

Maintaining Portals and Zones

The framework system has the dashboard, which can contain a configurable set of zones that show diverse information. Each product can have its own set of portals for e.g. the Oracle Utilities Customer Care and Billing system has two portals, the account information page in Control Central and the customer information page apart from the dashboard. 

Example Zone

This section describes how to create and implement your own custom zones and use them on the existing portals provided by the application.

Required Background.  As a zone developer you should have some familiarity with HTML.  Further, experience with Extensible Markup Language (XML) and XML Stylesheet Language Transform (XSLT) is very useful, because XSLT technology provides the easiest way to render information returned from service calls.

For more information on this topic, please refer to user documents Framework Administration, User Interface Tools, The Big Picture of Portals and Zones and Setting Up Portals and Zones.

 

Contents

Implementing Custom Zones

Key Dependence

Creating a New Zone

Debugging

Simple Example: LinkValueGrid

Another Example: accountFinancialHistory

The Service Data Buffer

XSLT Debugging

HTML Standards

Implementing Custom Zones

Portal zones are implemented as pieces of a single portal HTML page.  The portal framework wraps each zone inside a div element, and provides a title bar and collapse/expand widgets.  Note that zones are not implemented as independent iframes (though the internals of a zone could be).

Zones can be configured to be initially collapsed when the portal page loads, and then execute a deferred load when they are expanded.  This imposes some technical limitations that are discussed below.

While most zones do not depend on anything other than the current global context keys, some dashboard zones are context-sensitive, meaning they depend on the keys of the current object being displayed.

There are two components that define a portal zone:

·         Metadata to define the zone and its parameters

·         A Java handler class

Key Dependence

Zones usually depend on one or more of the global context keys. These keys are derived from the global context lookup. These are lookup values that each application defines for itself. When the web app server boots, the application will enumerate the available lookup values and make this information available to the browser. For e.g. Oracle Utilities Customer Care and Billing has ACCT_ID, PER_ID and PREM_ID as its global context keys.

In addition, context-sensitive zones can depend on model keys.  For performance reasons, zones are reloaded intelligently as needed.  Hence, non-context dashboard zones generally redisplay only when one of the context keys changes.

Creating a New Zone

The simplest way to create a new zone is as follows:

·         Use the ServiceZoneHandler

·         Create a Page Service containing required data

·         Create or reuse XSLT template file

·         Define metadata declaring the zone and its parameters

Contents

Zone Types

Zone Metadata

Zone Types

A content zone is associated with a Java class (actually the interface com.splwg.base.web.portal.IPortalZoneHandler) that is responsible for creating the zone’s HTML.  When a portal needs to be rendered, the server instantiates a new handler instance for every zone for the request.  In principle the handler could do anything within the bounds of the J2EE architecture to create its content.  In practice, the vast majority of zones need to make a service call and create HTML from the result.  Fortunately the SimpleZoneHandler has been designed to make this easy, and uses XSLT to perform the transformation from the result data (as an XML document) into HTML.  You will usually not need to implement your own handler classes.

Contents

Zone Type Interfaces

Service Zone Type

Zone Type Interfaces

The interface for the IPortalZoneHandler is illustrated below:

package com.splwg.base.web.portal;

 

import java.io.IOException;

import java.io.OutputStream;

 

import javax.servlet.ServletException;

 

public interface IPortalZoneHandler {

 

    void handleRequest(IUserZone zone, OutputStream outStream) throws ServletException, IOException;

 

    void setParameter(String parameterName, String value);

}

 

The interface for the IUserZone is illustrated below:

package com.splwg.base.web.portal;

 

import java.io.IOException;

import java.io.OutputStream;

 

import javax.servlet.ServletContext;

import javax.servlet.http.HttpServletRequest;

 

public interface IUserZone {

 

    HttpServletRequest getRequest();

 

    ServletContext getServletContext();

 

    String getLanguage();

 

    boolean isLanguageLTR();

 

    String getSequenceId();

 

    String getName();

 

    void emitZoneLimitMessage(OutputStream out) throws IOException;

 

    void emitGetAll(OutputStream out) throws IOException;

}

Note.  OutputStream is a binary stream for UTF-8 encoded characters.

Service Zone Type

You will usually use the ServiceZoneHandler, which also requires you to define a page service to provide the underlying business data for the zone.  The ServiceZoneHandler is a powerful, generic handler that is suitable for a large number of practical zone implementations.

This handler:

·         Retrieves a page service buffer and converts it to XML with appropriate localization

·         Executes XSLT transform on the result

·         Can display errors

·         Is performance optimized with stylesheet caching

In a development environment you may want to flush the stylesheet cache.  Simply invoke the flushPortalMetaInfo.jsp to clear it.

The following table describes the ServiceZoneHandler parameters:

Parameter

Description

SERVICE

The page service to retrieve data, e.g. CILFAFHP

XSLURL

Path to XSLT file (for example, WEB-INF/xsl/linkValueGrid.xsl)

Your files should be under /cm/xsl.

ALLOW_GET_ALL

Allow display of “Get All” button if the data that you want to show won’t fit in one buffer.

KEY1 - KEY5

Defines required keys.  If one of these keys is empty the zone is not rendered (unless null keys are allowed, see below). 

ALLOW_NULL_KEYS

If ALLOW_NULL_KEYS is “Y”, only one of the keys is required (any will work).

The following diagram illustrates example parameters for the service zone type:

 

Example Metadata for Service Zone Type

Zone Metadata

Portal Zone Metadata

Portals can contain multiple zones, and zones can be used in several portals.  Each user can have an independent definition of zones for his portal, via User Portal and User Zone.  A zone requires a zone type class, which takes parameters.  In addition, context-sensitive zones are associated with UI transactions (tab menus).  For security reasons a content zone is associated with an application service.

Debugging

There are several debugging facilities that help make portal zone development easier.

First, you should get the service working properly before worrying about the zone’s HTML. For e.g you can invoke the service through a browser and see the result (as localized XML) using this URL from a browser already logged-in to the Oracle Utilities Customer Care and Billing system:

http://<server>:<port>/portal?type=raw&service=CILFAFHP&ACCT_ID=5922116763&PER_ID=...

Note the required parameters are the service and the keys.  Don’t put quotes around string arguments.

The example below shows the output of the CILCALZP service (http://<server>:<port>/portal?type=raw&service=CILCALZP&ACCT_ID=5922116763):

<?xml version="1.0" encoding="UTF-8" ?>

<pageBody actionFlag="" metaInfoKey="CILCALZP">

  <list name="ZONE">

    <listHeader/>

    <listBody>

      <field type="string" name="FIELD_LABEL"></field>

      <field type="string" name="FIELD_VALUE">Comment Exists On Account</field>

      <field type="string" name="TOOLTIP_LBL_FIELD">GO_TO_ACCOUN_LBL</field>

      <field type="boolean" name="CHILD_ROW">false</field>

      <field type="string" name="SORT_KEY">ACCT</field>

      <field type="string" name="NAVIGATION_KEY">accountMaint</field>

      <field type="string" name="MENU_NAME"></field>

      <list name="KEY">

        <listHeader/>

        <listBody>

          <field type="string" name="KEY_NAME">ACCT_ID</field>

          <field type="string" name="KEY_VALUE">5922116763</field>

        </listBody>

      </list>

    </listBody>

    <listBody>

      <field type="string" name="FIELD_LABEL"></field>

      <field type="string" name="FIELD_VALUE">Account used in Billing test Plan</field>

      <field type="string" name="TOOLTIP_LBL_FIELD">GO_TO_ACCOUN_LBL</field>

      <field type="boolean" name="CHILD_ROW">false</field>

      <field type="string" name="SORT_KEY">ACCALT</field>

      <field type="string" name="NAVIGATION_KEY">accountMaint:9</field>

      <field type="string" name="MENU_NAME"></field>

      <list name="KEY">

        <listHeader/>

        <listBody>

          <field type="string" name="KEY_NAME">ACCT_ID</field>

          <field type="string" name="KEY_VALUE">5922116763</field>

        </listBody>

      </list>

    </listBody>

    <listBody>

      <field type="string" name="FIELD_LABEL"></field>

      <field type="string" name="FIELD_VALUE">Cable Customer</field>

      <field type="string" name="TOOLTIP_LBL_FIELD">GO_TO_SERVIC_LBL</field>

      <field type="boolean" name="CHILD_ROW">false</field>

      <field type="string" name="SORT_KEY">SATYPE</field>

      <field type="string" name="NAVIGATION_KEY">saMaint</field>

      <field type="string" name="MENU_NAME"></field>

      <list name="KEY">

        <listHeader/>

        <listBody>

          <field type="string" name="KEY_NAME">ACCT_ID</field>

          <field type="string" name="KEY_VALUE">5922116763</field>

        </listBody>

      </list>

    </listBody>

  </list>

</pageBody>

 

Simple Example: LinkValueGrid

The LinkValueGrid is a generic XSLT template that takes a standard copybook structure and creates an HTML table of clickable links.  It uses a standard include.xsl file.  As an example, consider the Alert grid.

Alerts Zone

Contents

XSLT File (/WEB-INF/xsl/linkValueGrid.xsl)

XML Meta Info

XSLT File (/WEB-INF/xsl/linkValueGrid.xsl)

The XSLT transform extracts fields by name from the service buffer XML document, and injects them into the HTML output.

Field

Role

FIELD_VALUE

Supplies displayed text.

NAVIGATION_KEY

Provides navigation option.

KEY

List of up to six context keys.

CHILD_ROW

Boolean that forces a slight left-indent.

The LabelValueGrid is similar, but it uses MENU_NAME to define the desired context menu.

(Reuse directly).

<?xml version="1.0" encoding="UTF-8" ?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSLTransform">

  <xsl:output method="html" />

  <xsl:strip-space elements="*" />

  <xsl:param name="sequenceId" />

  <xsl:param name="service" />

  <xsl:include href="include.xsl" />

  <xsl:include href="valueGridInclude.xsl" />

  <xsl:template match="listBody">

    <xsl:variable name="navKey" select="field[@name='NAVIGATION_KEY']" />

    <xsl:choose>

      <xsl:when test="string-length($navKey) > 0">

        <xsl:variable name="onclick">

                    <xsl:text />handleGotoContext('<xsl:value-of select="$navKey"/>'<xsl:text />

                    <xsl:call-template name="emitKeys" />

                    <xsl:text />)<xsl:text />

                </xsl:variable>

        <tr>

          <xsl:variable name="label">

            <xsl:call-template name="title" />

          </xsl:variable>

          <xsl:call-template name="rowClass" />

          <xsl:call-template name="linkValueCell">

            <xsl:with-param name="value" select="field[@name='FIELD_VALUE']" />

            <xsl:with-param name="onclick" select="$onclick" />

            <xsl:with-param name="indent" select="field[@name='CHILD_ROW']" />

            <xsl:with-param name="label" select="$label" />

          </xsl:call-template>

        </tr>

      </xsl:when>

      <xsl:otherwise>

        <xsl:call-template name="valueCell">

          <xsl:with-param name="value" select="field[@name='FIELD_VALUE']" />

          <xsl:with-param name="indent" select="field[@name='CHILD_ROW']" />

        </xsl:call-template>

      </xsl:otherwise>

    </xsl:choose>

  </xsl:template>

</xsl:stylesheet>

XML Meta Info

The following excerpt shows the XML Meta Info for the link value grid

<?xml version="1.0"?>

<page service="CILCALZP">

  <pageHeader>

    <boolean name="PAGE_READ_SW"/>

    <string name="ACCT_ID" size="10"/>

    <string name="PER_ID" size="10"/>

    <string name="PREM_ID" size="10"/>

    <string name="LAST_KEY_COMBINATION" size="100"/>

  </pageHeader>

  <pageBody>

    <list name="ZONE" size="60">

      <listHeader lastIndex="ZONE_COLL_CNT"></listHeader>

      <listBody>

        <string name="FIELD_LABEL" size="50"/>

        <string name="FIELD_VALUE" size="254"/>

        <string name="TOOLTIP_LBL_FIELD" size="18"/>

        <boolean name="CHILD_ROW"/>

        <string name="SORT_KEY" size="30"/>

        <string name="NAVIGATION_KEY" size="30"/>

        <string name="MENU_NAME" size="30"/>

        <list name="KEY" size="6">

          <listHeader lastIndex="KEY_COLL_CNT"></listHeader>

          <listBody>

            <string name="KEY_NAME" size="18"/>

            <string name="KEY_VALUE" size="30"/>

          </listBody>

        <list>

      </listBody>

    </list>

  </pageBody

</page>

Another Example: accountFinancialHistory

Zones for regular grids are not generic, but do contain a number of common components.  Clicking a column header sorts that column, according to its data type (string, number, date).

Example Account Financial History Grid

Contents

XSLT File (/WEB-INF/xsl/accountFinancialHistory.xsl)

XML Metainfo

XSLT File (/WEB-INF/xsl/accountFinancialHistory.xsl)

The XSLT transform “pulls” data from the XML result document into HTML by name-matching.

In examining the XSLT file, note the split between header/data rows.  You can see how the metainfo defining the data types for the columns is introduced, via the numberLabelCell and dateLabelCell.  You can see that the XSLT file minimizes explicit formatting (for example, of column widths), preferring to let the browser lay things out as it sees fit.

<?xml version="1.0" encoding="UTF-8" ?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSLTransform">

  <xsl:output method="html" />

  <xsl:strip-space elements="*" />

  <xsl:param name="sequenceId" />

  <xsl:include href="include.xsl" />

  <xsl:template match="/">

    <xsl:variable name="list" select="/pageBody/list[@name='ACCT_FT_HIST']/listBody" />

    <xsl:if test="count($list) > 0">

      <table class="dataTable" cellpadding="2" cellspacing="0">

        <tr class="zoneGridLabel">

          <xsl:call-template name="emptyCell" />

          <xsl:call-template name="dateLabelCell">

            <xsl:with-param name="key" select="'$ARS_DT'" />

          </xsl:call-template>

          <xsl:call-template name="labelCell">

            <xsl:with-param name="key" select="'$FT_TYPE_FLG'" />

          </xsl:call-template>

          <xsl:call-template name="numberLabelCell">

            <xsl:with-param name="key" select="'CI_FT$CUR_AMT'" />

          </xsl:call-template>

          <xsl:call-template name="numberLabelCell">

            <xsl:with-param name="key" select="'$CURRENT_BALAN_WRK'" />

          </xsl:call-template>

          <xsl:call-template name="numberLabelCell">

            <xsl:with-param name="key" select="'CI_FT$TOT_AMT'" />

          </xsl:call-template>

          <xsl:call-template name="numberLabelCell">

            <xsl:with-param name="key" select="'$DERIVED_AMT_WRK'" />

          </xsl:call-template>

        </tr>

        <xsl:apply-templates select="$list" />

      </table>

    </xsl:if>

  </xsl:template>

  <xsl:template match="listBody">

    <xsl:variable name="financialTransactionType">

      <xsl:value-of select="field[@name='FT_TYPE_FLG']" />

    </xsl:variable>

    <xsl:variable name="payEventId">

      <xsl:value-of select="field[@name='PAY_EVENT_ID']" />

    </xsl:variable>

    <xsl:variable name="parentId">

      <xsl:value-of select="field[@name='PARENT_ID']" />

    </xsl:variable>

    <xsl:variable name="siblingId">

      <xsl:value-of select="field[@name='SIBLING_ID']" />

    </xsl:variable>

    <tr>

      <xsl:attribute name="position">

        <xsl:value-of select="position()" />

      </xsl:attribute>

      <xsl:call-template name="rowClass" />

      <td class="gridTd" width="1">

        <img src="/images/goto_sm.gif" xsl:use-attribute-sets="imageButton" onclick="handleAccountFinancialHistoryContext('{$financialTransactionType}', '{$payEventId}', '{$parentId}', '{$siblingId}')" />

      </td>

      <xsl:variable name="currentAmount" select="field[@name='CUR_AMT']" />

      <xsl:variable name="currentBalance" select="field[@name='CUR_BAL']" />

      <xsl:variable name="payoffAmount" select="field[@name='TOT_AMT']" />

      <xsl:variable name="payoffBalance" select="field[@name='TOT_BAL']" />

      <xsl:call-template name="dateCell">

        <xsl:with-param name="value" select="field[@name='ARS_DT']" />

      </xsl:call-template>

      <xsl:call-template name="valueCell">

        <xsl:with-param name="value" select="field[@name='DESCR']" />

      </xsl:call-template>

      <xsl:call-template name="numberCell">

        <xsl:with-param name="value" select="$currentAmount" />

      </xsl:call-template>

      <xsl:call-template name="numberCell">

        <xsl:with-param name="value" select="$currentBalance" />

      </xsl:call-template>

      <xsl:call-template name="numberCell">

        <xsl:with-param name="value" select="$payoffAmount" />

        <xsl:with-param name="dimmed" select="$currentAmount = $payoffAmount" />

      </xsl:call-template>

      <xsl:call-template name="numberCell">

        <xsl:with-param name="value" select="$payoffBalance" />

        <xsl:with-param name="dimmed" select="$currentBalance = $payoffBalance" />

      </xsl:call-template>

    </tr>

    <script type="text/javascript" defer="defer">

            function handleAccountFinancialHistoryContext(financialTransactionType, payEventId, parentId, siblingId) {

                switch (financialTransactionType) {

                    case 'PS' :

                    case 'PX' : {

                        handleGotoContext('paymentEventMaint', 'PAY_EVENT_ID', payEventId);

                        break;

                    }

                    case 'BS' :

                    case 'BX' : {

                        handleGotoContext('billMaint', 'BILL_ID', parentId);

                        break;

                    }

                    case 'AD' :

                    case 'AX' : {

                        handleGotoContext('adjustmentMaint', 'ADJ_ID', siblingId);

                        break;

                    }

                }

            }

    </script>

  </xsl:template>

</xsl:stylesheet>

 

XML Metainfo

The following excerpt shows the XML Meta Info for the Account Financial History.

<?xml version="1.0"?>

<page service="CILFAFHP">

  <pageHeader>

    <boolean name="PAGE_READ_SW"/>

    <string name="ACCT_ID" size="10"/>

    <boolean name="LIMITED_SW"/>

  </pageHeader>

  <pageBody>

     <string name="ENTITY_NAME" size="64"/>

     <string name="ACCT_ID" size="10"/>

     <list name="ACCT_FT_HIST" size="25" service="CILFAFHL">

       <listHeader/>

         <string name="ACCT_ID" size="10"/>

         <boolean name="LIMITED_SW"/>

         <string name="LAST_PARENT_ID" size="14"/>

         <date name="LAST_ARS_DT"/>

         <string name="LAST_CURRENCY_CD" size="3"/>

         <string name="LAST_FT_TYPE_FLG" size="2"/>

         <money name="LAST_CUR_AMT" precision="15" scale="2"/>

         <money name="LAST_TOT_AMT" precision="15" scale="2"/>

         <money name="LAST_CUR_BAL" precision="15" scale="2"/>

         <money name="LAST_TOT_BAL" precision="15" scale="2"/>

       </listHeader>

       <listBody>

         <string name="ACCT_ID" size="10"/>

         <date name="ARS_DT"/>

         <string name="PARENT_ID" size="14"/>

         <string name="PAY_EVENT_ID" size="12"/>

         <string name="SIBLING_ID" size="12"/>

         <string name="FT_TYPE_FLG" size="2"/>

         <string name="DESCR" size="50"/>

         <money name="CUR_AMT" precision="15" scale="2"/>

         <money name="TOT_AMT" precision="15" scale="2"/>

         <money name="CUR_BAL" precision="15" scale="2"/>

         <money name="TOT_BAL" precision="15" scale="2"/>

         <currency tuxedo="CURRENCY_CD"/>

       </listBody>

     </list>

  </pageBody>

</page>

 

The Service Data Buffer

The following example output shows the Account Financial History Service data buffer converted to XML.

<?xml version="1.0" encoding="UTF-8" ?>

<pageBody actionFlag="" metaInfoKey="CILFAFHP">

  <field type="string" name="ENTITY_NAME">Brazil,Mark J  - Commercial</field>

  <field type="string" name="ACCT_ID">5922116763</field>

  <list name="ACCT_FT_HIST" service="CILFAFHL">

    <listHeader actionFlag="" hasMoreRows="true" alertRowIndex="0">

      <field type="string" name="ACCT_ID">5922116763</field>

      <field type="boolean" name="LIMITED_SW">false</field>

      <field type="string" name="LAST_PARENT_ID">592211607251</field>

      <field type="date" name="LAST_ARS_DT">1998-02-16</field>

      <field type="string" name="LAST_CURRENCY_CD">USD</field>

      <field type="string" name="LAST_FT_TYPE_FLG">PS</field>

      <field type="money" name="LAST_CUR_AMT">-159.33</field>

      <field type="money" name="LAST_TOT_AMT">-159.33</field>

      <field type="money" name="LAST_CUR_BAL">0.00</field>

      <field type="money" name="LAST_TOT_BAL">0.00</field>

    </listHeader>

    <listBody actionFlag="">

      <field type="string" name="ACCT_ID">5922116763</field>

      <field type="date" name="ARS_DT"></field>

      <field type="string" name="PARENT_ID">592211609883</field>

      <field type="string" name="PAY_EVENT_ID"></field>

      <field type="string" name="SIBLING_ID">592211683162</field>

      <field type="string" name="FT_TYPE_FLG">BS</field>

      <field type="string" name="DESCR">Bill Segment</field>

      <field type="money" name="CUR_AMT">177.42</field>

      <field type="money" name="TOT_AMT">177.42</field>

      <field type="money" name="CUR_BAL">-5.50</field>

      <field type="money" name="TOT_BAL">-5.50</field>

      <field type="currency" name="CURRENCY_CD">USD</field>

    </listBody>

    <listBody actionFlag="">

      <field type="string" name="ACCT_ID">5922116763</field>

      <field type="date" name="ARS_DT">2003-03-17</field>

      <field type="string" name="PARENT_ID">592211687290</field>

      <field type="string" name="PAY_EVENT_ID">592211628707</field>

      <field type="string" name="SIBLING_ID">592211605468</field>

      <field type="string" name="FT_TYPE_FLG">PS</field>

      <field type="string" name="DESCR">Pay Segment</field>

      <field type="money" name="CUR_AMT">-5.50</field>

      <field type="money" name="TOT_AMT">-5.50</field>

      <field type="money" name="CUR_BAL">-182.92</field>

      <field type="money" name="TOT_BAL">-182.92</field>

      <field type="currency" name="CURRENCY_CD">USD</field>

    </listBody>

  </list>

</pageBody>

XSLT Debugging

There are some techniques to help debug your XSLT files.  Malformed XSLT will cause error messages to appear in the WebLogic console.  In addition, you can test the layout of a portal zone in isolation using this URL from a browser that is logged-in to the Oracle Utilities Customer Care and Billing system:

http:<server>:<port>/portal?type=xslTest&service=CILCALZP&xslURL=/WEB-INF/xsl/linkValueGrid.xsl&ACCT_ID=5922116763

(Ignore any JavaScript errors)

The important parameters are the service, the xslURL, and the service keys.

HTML Standards

Since portal zones are simply div elements within a single HTML page, they must co-exist harmoniously (for example, don’t assume HTML IDs are unique).

Here are some tips to avoid problems:

Avoid hard-coding sizes (e.g. widths).  It’s best to let the browser manage the resizing of zones when the browser window is resized by the user.  One tip: Use width 100% for tables and divs.

There is an IE Bug with JavaScript in documents loaded after the main page has loaded, so-called deferred loading. To support deferred loading, JavaScript tags must use the defer attribute:

<script type=“text/javascript” defer=“defer”>

 

Further, such JavaScript code should appear at the bottom of the zone.  Refer to the existing XSLT files for examples.

Rely on the standard Oracle Utilities Customer Care and Billing cascading style sheets (cisDisabled.css), which are automatically loaded in the portal page.  Some useful style classes are “normal”, “label”, and “biggerText”.

Since an HTML page provides a single global namespace for widget IDs, avoid hard-coding HTML IDs.  If you absolutely must, you may want to make use of the sequenceId XSLT template variable, which provides a unique ID to every zone when it renders.

 

 

Maintaining Background Processes

Contents

Maintaining Background Processes Overview

Creating a BatchJob

Creating a ThreadWorker

Maintaining Background Processes Overview

Each new background processes require the creation of two new classes: a BatchJob and a ThreadWorker.  These classes fit into the “master-worker” pattern used by the background process runtime infrastructure to overcome the throughput limitations encountered by single-threaded processes.  By splitting work among many concurrent threads often on multiple physical nodes background processes can achieve excellent scalability and react to changing work demands without additional programming.  In this pattern:

·         A BatchJob is responsible for determining the work to be processed for a batch run and then splitting that work into pieces that each ThreadWorker will process.  When running a single process, a single BatchJob object is instantiated by the framework.  The framework then makes calls to the BatchJob instance at the appropriate time.  One such set of calls to the BatchJob instance is to return to the framework a collection of ThreadWork instances that will be distributed for execution.

·         A ThreadWorker is responsible for processing a single ThreadWork instance for a run.  Within the ThreadWork there are many WorkUnits representing the fine-grained units of work to be processed.  In many cases the WorkUnits represent a complete database transaction, for example, a bill being created for an account.  Whether or not the ThreadWorker executes on the same computer as other ThreadWorkers or the BatchJob that created its work is left as a configuration choice to be made at runtime.  Within a single process, there may be many ThreadWorker objects.  In general, each ThreadWorker instantiated in a batch run has a corresponding row in the Batch Instance table.  The Batch Instance rows provide persistent state that is needed for the ThreadWorkers to operate correctly in failure/restart situations.

 

Creating a BatchJob

A BatchJob class is responsible for determining what work needs to be done within the run and splitting the work among ThreadWorkers.

Contents

The BatchJob Annotation

Creating JobWork

Declaring a ThreadWorker Class

The BatchJob Annotation

Each BatchJob class must declare a BatchJob annotation that specifies important attributes of the job.  An example is shown below:

@BatchJob (rerunnable = false,

multiThreaded = true,

modules={todo},

softParameters = { @BatchJobSoftParameter

(name=OUTPUT-DIR, type=string) },

toDoCreation = @ToDoCreation (drillKeyEntity = user,

sortKeys = {lastName, firstName},

messageParameters = {firstName, lastName}

)

)

The annotation declares if the job can be rerun, supports more than one thread of operation, the modules that the job belongs to, its nonstandard runtime parameters and the details of how “ToDo” entries should be created in the case of errors.  When not specified in the annotation, default values will be used.

Creating JobWork

The most important goal of a BatchJob class is to return an instance of JobWork describing what work should be done (ThreadWorkUnits) and have that work split into manageable chunks (ThreadWork) that can be processed by a single ThreadWorker.

Most commonly, ThreadWorkUnits contain only the ID values of the entities to be processed.  For example, one can envision a process that performs an operation on a set of accounts.  In general, one would expect that each ThreadWorkUnit would contain a single AccountId.  The ThreadWorker objects would then be constructed in such a way that when asked to execute for a ThreadWorkUnit it would pull out the embedded AccountId and then perform the required business function.

There are convenience methods available from the AbstractBatchJob that make it easier to create JobWork instances.  For example, the createJobWorkForEntityQuery(Query) method will accept a query returning BusinessEntity instances and create a JobWork instance containing the appropriate number of ThreadWork instances each containing (notwithstanding rounding) the same number of ThreadWorkUnits.

Declaring a ThreadWorker Class

It is the responsibility of the BatchJob to declare what class defines the ThreadWorkers that should perform the work.  By returning a Class instance rather than ThreadWorker instances, the framework controls ThreadWorker instantiation which may occur on a different JVM than the one that the BatchJob instance resides.

Creating a ThreadWorker

The ThreadWorker performs the “heavy lifting” of a batch process.  For a given run, there will ThreadWorkers created equal in number to the “thread count” parameter provided when a process is requested.  

Contents

Initializing ThreadWork

Executing a WorkUnit

Finalizing ThreadWork

Choosing a ThreadExecutionStrategy

Initializing ThreadWork

Each ThreadWorker instance can expect to have its initializeThreadWork() method called once by the framework before any actual work is to be performed.  This method may be implemented to do any setup necessary for that thread’s execution, most commonly output files opened or variables initialized.

Warning!  It is very important that any setup necessary to execute a WorkUnit is done here and not in the creation of JobWork, this includes accessing batch parameters.  There is no guarantee that static variables set at the time of JobWork creation will be available at this time.  The framework may be calling ThreadWork in a different process from the creation of JobWork.

Executing a WorkUnit

The ThreadWorker can expect that its executeWorkUnit method will be called once for each ThreadWorkUnit that that ThreadWorker will process.  For example, if the batch process will act upon 10,000 accounts and the process is submitted with a ThreadCount=10, we can expect that there are 10 ThreadWorkers created by the framework and each worker will have its executeWorkUnit method called by the framework for each of the 1,000 ThreadWorkUnits allocated to that thread.

Finalizing ThreadWork

Each ThreadWorker instance can expect to have its finalizeThreadWork() method called once after all ThreadWorkUnits have been processed.  This gives the opportunity to close any open files or to do any other “tear down” processing for the ThreadWorker.

Choosing a ThreadExecutionStrategy

ThreadWork instances need to provide a strategy defining the execution policies for its work.  That is, how the work for a thread will be processed.  The interface that is implemented is ThreadExecutionStrategy.  The most important aspect of this is how exceptions will be treated with respect to transactions.

·         Should all the ThreadWorkUnits be wrapped in a single transaction with a single rollback on an exception?

·         Should each ThreadWorkUnit be in its own transaction?

·         Should the framework attempt to process many ThreadWorkUnits within a single transaction?

·         If an exception occurs should the framework "back up" and reprocess the successful units?

In general, new background processes are expected to chose from existing instances of ThreadExecutionStrategy, not create new ones.  Please scan for existing implementations of ThreadExecutionStrategy.

Building the Application Viewer

Contents

Creating Table XMLs

Creating MO XMLs

Creating Algorithm XMLs

Extending Service XMLs

Creating Javadocs for CM Source code.

Creating Table XMLs

Run batch process F1AVGTBL.

Creating MO XMLs

Run batch process F1AVGMO.

Creating Algorithm XMLs

Run batch process F1AVGALG.

Extending Service XMLs

When new application services are created, new Service XMLs are also created as part of the process. These files may be accessed via the Application Viewer by copying them to the CM folder for Service XMLs.

“AVLOC\data\xml\CM”

Where AVLOC is the path to the Application Viewer folder (ie. C:\appViewer).

Creating Javadocs for CM Source code.

Sun’s reference on the Javadoc tool can be found at the following location: http://java.sun.com/j2se/javadoc/reference/index.html.  Please refer to the documentation concerning how to write tags, troubleshoot warnings and errors, and any other Javadoc tool questions.

Some known warnings are generated as part of the CM Javadoc process.  The following two warnings are safe to ignore:

·         The product’s annotations currently use tags that are unrecognized by the Javadoc tool.  Currently, the Javadoc tool is reporting these as warnings.  These warnings are safe to ignore.  For the list of tags that are relevant, please refer to the reference guide.  For example, the Javadoc tool emits the following warning when it encounters the product’s EntityPageMaintenance annotation.  It is safe to ignore.

warning - @EntityPageMaintenance is an unknown tag

·         The Javadocs tool may also generate warnings that appear from the generated artifacts.  These are easily identifiable by looking at the path for the name “sourcegen.”  For example the following warning can be ignored since path name includes the “sourcegen” directory.

C:\spl\CCB_PROJ1\java\sourcegen\cm\com\splwg\cm\domain\common\cmCisDiv\CmCisDivisionMaintenance_Gen.java:437

For all other warnings, please refer to the Sun’s documentation.

To generate Javadocs, run the utility script generateJavadoc.bat.

To integrate CM and the product’s Javadocs, run the utility script reindexJavadoc.bat to recreate the indices to reflect current environment.

Upgrade JSP to XSLT

Trees and subpanels should be upgraded to use the application’s XSLTs instead of the JSPs used in v1.5.x.  This section describes the upgrade process.

Note that all other JSPs (tab pages, list grids, etc) must have been upgraded to XSLTs in v1.5.x.  Thus, there is no tool to upgrade such code in V2.

Contents

Create User Exit Files

Tree User Exit Changes

Change Template Code in Program Components

Create XML File with UI Meta-data

Delete the JSP Files

Log Into the Application and Test

Create User Exit Files

The user exits in the JSP-based system were directly placed within the JSP as code snippets within specially located markers. In the XSLT system, the meta-data is separated from the user exit, and resides in an .xjs file with the same name as the JSP file, with only user exits and each user exit function explicitly defined in the file.

Going from JSP to XSLT user exits is thus not trivial. However, a set of supplied scripts will create a new user exit .xjs file for each of the different JSP template files in your system.  The following table lists the scripts to run for each template file:

Sub Panel

convertSubPanel.pl

Tree Page

convertTreePage.pl

Tree User Exit Changes

Since the XSLT user exits are now callout functions, five tree user exits need to be coded differently.  The main purpose of these five user exits is to change a variable’s value.  The analogous new XSLT user exits return the desired value instead.

The user exits have been renamed to more accurately reflect their new function.  Below is a comparison of names and purpose of JSP-based user exits versus XSLT-based user exits.

JSP name

Main purpose

XSLT name

Main purpose

setServiceIndex

Sets the desired serviceIndex variable.

overrideServiceIndex

Returns the desired index of the service.

setNavKey

Sets the desired newNavKey variable.

overrideNavKey

Returns the desired nav key.

setNavKeyIndex

Sets the desired navKeyIndex variable.

overrideNavKeyIndex

Returns the desired index of the nav key.

setImageOpenIndex

Sets the desired imageIndex variable.

overrideImageOpenIndex

 

Returns the desired index of the open image.

setImageClosedIndex

Sets the desired imageIndex variable.

overrideImageClosedIndex

Returns the desired index of the closed index.

 

Each user exit is passed the variable’s original value.  If the user exit does not return a value, the original variable’s value will be used.

Below is an example of a JSP user exit and a converted XSLT user exit inside an .xjs file.

Here is the JSP user exit.

 

// $#BSES SETSERVICE

if (nodeName == 'newtype') {

   var myLetter = pageKeys.FT_TYPE.substr(0,1);

   if (myLetter == 'A') {

     serviceIndex = 1;

   }

   if (myLetter == 'B') {

     serviceIndex = 2;

   }

   if (myLetter == 'C') {

     serviceIndex = 3;

   }

   if (myLetter == 'P') {

     serviceIndex = 4;

   }

 }

// $#BSEE SETSERVICE

 

Here is the same user exit coded in an .xjs file:

function overrideServiceIndex(nodeName, services, pageKeys, serviceIndex) {

   var overrideIndex;

   if (nodeName == 'newtype') {

      var myLetter = pageKeys.FT_TYPE.substr(0,1);

      if (myLetter == 'A') {

        overrideIndex = 1;

      }

      if (myLetter == 'B') {

        overrideIndex = 2;

      }

      if (myLetter == 'C') {

        overrideIndex = 3;

      }

      if (myLetter == 'P') {

        overrideIndex = 4;

      }

   }

return overrideIndex;

}

Change Template Code in Program Components

The meta-data on the database for the CM JSP program component pages needs to point to the new XSLT template codes. There is a set of SQL scripts that update all the CM tree and sub panel program components with the correct template.

Run the SQLs in the script changeTemplateCodesTTRAndPN.sql against your database to perform this change.

Create XML File with UI Meta-data

The XSLT framework uses the meta-data at run-time to drive the transform. However, it does not query the database for this, and instead relies upon an intermediate representation in the form of an XML file stored with the same name (but with an .xjs extension) and location as the original JSP file.

The XML file is automatically created by the framework the first time the page is viewed if an existing XML file does not exist.  Delete the existing XML file, if one exists, located in the same directory as the original JSP file.  The XML file is named after the program component’s name. To generate the XML file, view the program component from within the application.

Delete the JSP Files

Once the meta-data is changed and the new files are properly placed, there is no longer a need for the JSP files for the converted program components, and it would be a good idea to delete them to avoid confusion.

Find the JSP files in the file system and delete them.

Log Into the Application and Test

For the new XSLT pages to be used by the system, instead of the system looking for the old JSPs, some server and browser caches need to be flushed. The easiest thing to do is restart the app server and start a new browser session.

Login and visit the converted pages to test functionality.