IBM Support

Object Level Configurative Action and Exception Structure similar to Automation Scripts (v7.1)

Technical Blog Post


Abstract

Object Level Configurative Action and Exception Structure similar to Automation Scripts (v7.1)

Body

Maximo/Control Desk product introduced Automation Scripts instead of Java customizations on version 7.5 and enhanced the scripting functionality on version 7.6. Yet, there are many customer environments which have not been upgraded to latest versions. The following customization had been created for a customer environment which was in version 7.1.

The aim of this development is to present a generic and configurative structure to replace and prevent continuous Java customization.

 

1. Adding the custom business objects to hold restriction definitions

First of all, we add two custom objects (MXM_MAINRULES, MXM_SUBRULES) via Database Configuration application which will be used to store the rules for actions or exceptions. MXM_MAINRULES should be defined as a main object. Indeed only one table is enough to store the rules but considering redundancy and performance issues the rules are split into two tables. We filter the objects and common conditions with main rule table and then filter the action/exception specific conditions with sub rule table. The attribute list for the custom objects can be seen in Table 1 and  Table 2 below.

 

Field ID Description Type Length Required? Usage
ACTIVE Is Active? YORN 1 Y Field default value is 1. It is set to 0 for inactive rules.
MAINRULE Main Rule Identifier UPPER 20 Y Identification field for main rule.
SEQUENCE Main Rule Sequence SMALLINT 10 Y Ordering of main rules for execution.
OBJECTNAME Object Name UPPER 30 Y Same as MAXOBJECT.OBJECTNAME field. It defines the object used for the main rule.
DESCRIPTION Description ALN 100 N The description or comment for the main rule record.
MXM_MAINRULESID Unique Identifier BIGINT 19 Y System set unique identifier for the main rule record.
MAINCONDITION Main Condition Identifier ALN 12 Y Same as the CONDITION.CONDITIONNUM field. It defines the condition for the main rule.
TYPE Operation Type ALN 10 Y Type of the operation: INSERT, UPDATE, ALL.

Table 1 - Attribute List of Main Rules Object

 

Field ID Description Type Length Required? Usage
ACTIVE Is Active? YORN 1 Y Field default value is 1. It is set to 0 for inactive rules.
MAINRULE Main Rule Identifier UPPER 20 Y Identification field for main rule.
SUBRULE Sub Rule Identifier UPPER 20 Y Identification field for sub rule.
SEQUENCE Main Rule Sequence SMALLINT 10 Y Ordering of main rules for execution.
OBJECTNAME Object Name UPPER 30 Y Same as MAXOBJECT.OBJECTNAME field. It defines the object used for the main rule.
DESCRIPTION Description ALN 100 N The description or comment for the sub rule record.
MXM_SUBRULESID Unique Identifier BIGINT 19 Y System set unique identifier for the sub rule record.
SUBCONDITION Sub Condition Identifier ALN 12 Y Same as the CONDITION.CONDITIONNUM field. It defines the condition for the sub rule.
TYPE Operation Type ALN 10 Y Type of the operation: ACTION, ERROR.
ACTION Action Identifier ALN 30 N Same as the ACTION.ACTION field. It defines the action used for the sub rule.
MSGGROUP Message Group ALN 25 N Same as the MAXMESSAGES.MSGGROUP field. It defines the message key used for the sub rule.
MSGKEY Message Key ALN 40 N Same as the MAXMESSAGES.MSGKEY field. It defines the message key used for the sub rule.

Table 2 - Attribute List of Sub Rules Object

 

After that we turn on Admin mode, apply Database configurations and turn Admin mode off. Then we add relationships, seen in Table 3 below, to/from our new custom objects, which will be used in the custom Java class and application to access remote objects.

 

Relation Name Parent Object Child Object Where Clause
MXM_MAINRULES WORKORDER MXM_MAINRULES active = 1 and objectname = 'WORKORDER'
MXM_MAINRULES_INSERT WORKORDER MXM_MAINRULES active = 1 and objectname = 'WORKORDER' and type in ('INSERT', 'ALL') order by sequence asc
MXM_MAINRULES_UPDATE WORKORDER MXM_MAINRULES active = 1 and objectname = 'WORKORDER' and type in ('UPDATE', 'ALL') order by sequence asc
MXM_SUBRULES MXM_MAINRULES MXM_SUBRULES 1 = 1
MXM_SUBRULES_ERRORS MXM_MAINRULES MXM_SUBRULES active = 1 and mainrule = :mainrule and type = 'ERROR'
MXM_SUBRULES_ACTIONS MXM_MAINRULES MXM_SUBRULES active = 1 and mainrule = :mainrule and type = 'ACTION'
ACTION MXM_SUBRULES ACTION action = :action

Table 3 - Relationship Definitions related to Main Rules and Sub Rules

 

With the relations defined our custom main rule and sub rule tables are ready to use. We also need to add the proper domains to the elective fields, this part is left to the implementer. In the next steps we will implement the user interface customizations and write the custom Java classes.

NOTE: If we intend to use this structure for more objects like SR, INCIDENT, ASSET etc. then we also add the proper relations for these objects as we did for WORKORDER in Table 3.

 

2. Adding Custom Rules Application which will be used to view and edit rules

In this section, we will add the user interface for custom rules. Using Application Designer application, we create a single page application, i.e. customrule, for MXM_MAINRULES object under any module we want. Then, we import customrule.xml into the system which can be seen below:

 

<?xml version="1.0" encoding="UTF-8"?>
<presentation id="customrule" ismobile="false" mboname="MXM_MAINRULES" version="6.0.0" viewport="1222x704">
    <page id="mainrec" scroll="false">
        <include controltoclone="single_pageHeader" id="INCLUDE-single_pageHeader"/>
        <clientarea id="clientarea">
            <datasrc id="mxm_subrules" relationship="MXM_SUBRULES"/>
            <tabgroup format="carddeck" id="maintabs" style="form">
                <tab default="true" id="results">
                    <section id="section_0">
                        <table id="resultsTable" label="Main Rules">
                            <tablebody displayrowsperpage="10" filterable="true" filterexpanded="true" id="resultsTablebody">
                                <tablecol filterable="false" id="results_col_details" mxevent="toggledetailstate" sortable="false" type="event"/>
                                <tablecol dataattribute="mainrule" id="results_showlist_column1" label="Main Rule"/>
                                <tablecol dataattribute="objectname" id="results_showlist_column2" label="Object Name" lookup="valuelist"/>
                                <tablecol dataattribute="description" id="results_showlist_column3" label="Description"/>
                                <tablecol dataattribute="maincondition" id="results_showlist_column4" label="Main Condition" lookup="valuelist"/>
                                <tablecol dataattribute="type" id="results_showlist_column5" label="Type" lookup="valuelist"/>
                                <tablecol dataattribute="sequence" id="results_showlist_column6" label="Sequence"/>
                                <tablecol dataattribute="active" id="results_showlist_column7" label="Active?" lookup="valuelist"/>
                                <tablecol filterable="false" id="results_col_delete" mxevent="toggledeleterow" mxevent_desc="Mark Row for Delete" mxevent_icon="btn_garbage.gif" sortable="false" type="event"/>
                            </tablebody>
                            <tabledetails id="resultsTabledetails">
                                <section id="resultsTabledetails_tabledetails_1" label="Details">
                                    <sectionrow id="resultsTabledetails_tabledetails_1_1">
                                        <sectioncol id="resultsTabledetails_tabledetails_1_1_1">
                                            <section id="resultsTabledetails_tabledetails_1_1_1_1">
                                                <textbox dataattribute="mainrule" id="resultsTabledetails_tabledetails_1_1_1_1_1"/>
                                                <textbox dataattribute="objectname" id="resultsTabledetails_tabledetails_1_1_1_1_2" lookup="valuelist"/>
                                                <textbox dataattribute="description" id="resultsTabledetails_tabledetails_1_1_1_1_3"/>
                                                <textbox dataattribute="maincondition" id="resultsTabledetails_tabledetails_1_1_1_1_4" lookup="valuelist"/>
                                            </section>
                                        </sectioncol>
                                        <sectioncol id="resultsTabledetails_tabledetails_1_1_2">
                                            <section id="resultsTabledetails_tabledetails_1_1_2_1">
                                                <textbox dataattribute="type" id="resultsTabledetails_tabledetails_1_1_2_1_1"/>
                                                <textbox dataattribute="sequence" id="resultsTabledetails_tabledetails_1_1_2_1_2"/>
                                                <checkbox dataattribute="active" id="resultsTabledetails_tabledetails_1_1_2_1_3"/>
                                            </section>
                                        </sectioncol>
                                    </sectionrow>
                                </section>
                            </tabledetails>
                            <buttongroup id="resultsButtongroup">
                                <pushbutton default="true" id="results_button_1" label="New Main Rule" mxevent="addrow"/>
                            </buttongroup>
                        </table>
                        <blankline id="blank_line_1"/>
                        <table id="resultsTable2" label="Sub Rules">
                            <tablebody displayrowsperpage="20" filterable="true" filterexpanded="true" id="resultsTablebody2" datasrc="mxm_subrules">
                                <tablecol filterable="false" id="results_col_details2" mxevent="toggledetailstate" sortable="false" type="event"/>
                                <tablecol dataattribute="subrule" id="results_showlist2_column0" label="Sub Rule"/>
                                <tablecol dataattribute="mainrule" id="results_showlist2_column1" label="Main Rule"/>
                                <tablecol dataattribute="objectname" id="results_showlist2_column2" label="Object Name" lookup="valuelist"/>
                                <tablecol dataattribute="description" id="results_showlist2_column3" label="Description"/>
                                <tablecol dataattribute="subcondition" id="results_showlist2_column4" label="Sub Condition" lookup="valuelist"/>
                                <tablecol dataattribute="type" id="results_showlist2_column5" label="Type" lookup="valuelist"/>
                                <tablecol dataattribute="action" id="results_showlist2_column6" label="Action" lookup="valuelist"/>
                                <tablecol dataattribute="msggroup" id="results_showlist2_column7" label="Message Group" lookup="valuelist"/>
                                <tablecol dataattribute="msgkey" id="results_showlist2_column8" label="Message Key" lookup="valuelist"/>
                                <tablecol dataattribute="sequence" id="results_showlist2_column9" label="Sequence"/>
                                <tablecol dataattribute="active" id="results_showlist2_column10" label="Active?" lookup="valuelist"/>
                                <tablecol filterable="false" id="results_col_delete2" mxevent="toggledeleterow" mxevent_desc="Mark Row for Delete" mxevent_icon="btn_garbage.gif" sortable="false" type="event"/>
                            </tablebody>
                            <tabledetails id="resultsTabledetails2">
                                <section id="resultsTabledetails2_tabledetails_1" label="Details">
                                    <sectionrow id="resultsTabledetails2_tabledetails_1_1">
                                        <sectioncol id="resultsTabledetails2_tabledetails_1_1_1">
                                            <section id="resultsTabledetails2_tabledetails_1_1_1_1">
                                                <textbox dataattribute="subrule" id="resultsTabledetails2_tabledetails_1_1_1_1_0"/>
                                                <textbox dataattribute="objectname" id="resultsTabledetails2_tabledetails_1_1_1_1_1" lookup="valuelist"/>
                                                <textbox dataattribute="description" id="resultsTabledetails2_tabledetails_1_1_1_1_2"/>
                                                <textbox dataattribute="subcondition" id="resultsTabledetails2_tabledetails_1_1_1_1_3" lookup="valuelist"/>
                                                <textbox dataattribute="action" id="resultsTabledetails_tabledetails2_1_1_1_1_4" lookup="valuelist"/>
                                                <textbox dataattribute="msggroup" id="resultsTabledetails_tabledetails2_1_1_1_1_5" lookup="valuelist"/>
                                                <textbox dataattribute="msgkey" id="resultsTabledetails_tabledetails2_1_1_1_1_6" lookup="valuelist"/>
                                            </section>
                                        </sectioncol>
                                        <sectioncol id="resultsTabledetails2_tabledetails_1_1_2">
                                            <section id="resultsTabledetails2_tabledetails_1_1_2_1" inputmode="readonly">
                                                <textbox dataattribute="mainrule" id="resultsTabledetails2_tabledetails_1_1_2_1_1" lookup="valuelist"/>
                                                <textbox dataattribute="type" id="resultsTabledetails2_tabledetails_1_1_2_1_2" lookup="valuelist"/>
                                                <textbox dataattribute="sequence" id="resultsTabledetails2_tabledetails_1_1_2_1_3"/>
                                                <checkbox dataattribute="active" id="resultsTabledetails2_tabledetails_1_1_3_1_4"/>
                                            </section>
                                        </sectioncol>
                                    </sectionrow>
                                </section>
                            </tabledetails>
                            <buttongroup id="resultsButtongroup2">
                                <pushbutton default="true" id="results_button2_1" label="New Sub Rule" mxevent="addrow"/>
                            </buttongroup>
                        </table>
                    </section>
                </tab>
            </tabgroup>
        </clientarea>
        <include controltoclone="pageFooter" id="INCLUDE-pageFooter"/>
    </page>
    <configurationblock id="datastore_configurationblock">
    </configurationblock>
</presentation>

After importing customrule.xml into the system, we can edit the user interface further (change labels or give input modes etc.) using the Application Designer canvas.

 

3. Development of Java Classes in order to process the custom rules


The creation of custom objects and application modifications were for defining and viewing the custom rules. After that, we need to add the logic to the system which will process the conditions, actions and errors defined in main and sub rules. Here, we create a new custom class (CustomRules.java) which will be used in Maximo objects in common:

package com.custom.common;

import java.rmi.RemoteException;

import psdi.common.action.ActionRemote;
import psdi.common.action.ActionSetRemote;
import psdi.mbo.MboRemote;
import psdi.mbo.MboSetRemote;
import psdi.util.MXApplicationException;
import psdi.util.MXException;

public class CustomRules {

    // Method to be called within MBOs like WO, SR, etc. 
    public static void processRules(MboRemote mbo) throws RemoteException, MXException {
        // if the record is new then we get all main rules except UPDATE only, otherwise all except INSERT only
        String mainRelation = mbo.isNew() ? "MXM_MAINRULES_INSERT" : "MXM_MAINRULES_UPDATE";
        MboSetRemote mainRules = mbo.getMboSet(mainRelation);
        for (MboRemote mainRule = mainRules.moveFirst(); mainRule != null; mainRule = mainRules.moveNext()) {
            // we evaluate the condition of main rule and if it returns true we check its sub rules
            if(mbo.evaluateCondition(mainRule.getString("MAINCONDITION"))){
                // we first check sub rules of errors
                MboSetRemote subErrors = mainRule.getMboSet("MXM_SUBRULES_ERRORS");         
                for (MboRemote subError = subErrors.moveFirst(); subError != null; subError = subErrors.moveNext()) {
                    // we evaluate the condition of sub rule and throw error if it returns true
                    if (mbo.evaluateCondition(subError.getString("SUBCONDITION"))) {
                        String msgGroup = subError.getString("MSGGROUP");
                        String msgKey = subError.getString("MSGKEY");       
                        throw new MXApplicationException(msgGroup, msgKey);
                    }
                }
                subErrors.close();
                // secondly we check sub rules of actions 
                MboSetRemote subActions = mainRule.getMboSet("MXM_SUBRULES_ACTIONS");
                for (MboRemote subAction = subActions.moveFirst(); subAction != null; subAction = subActions.moveNext()) {
                    // we evaluate the condition of sub rule and run the actions if it returns true
                    if(mbo.evaluateCondition(subAction.getString("SUBCONDITION"))){
                        runAction(subAction, mbo);
                    }
                }
                subActions.close();
            }
        }   
        mainRules.close();
    }

    // Method to be called in order to run Maximo actions or group actions
    private static void runAction(MboRemote rule, MboRemote mbo) throws MXException, RemoteException {
        ActionSetRemote actSet = (ActionSetRemote) rule.getMboSet("ACTION");
        if (!actSet.isEmpty()) {
            ActionRemote action = (ActionRemote) actSet.getMbo(0);
            if(action != null){
                action.executeAction(mbo);
            }
        }
        actSet.close();
    }

}

The new custom class should be put into maximo.ear/businessobjects.jar/com/custom/common folder. Then, we inject our logic to the MBO classes by extending MBO classes and overriding save method:

package com.custom.app.workorder;

import java.rmi.RemoteException;

import psdi.mbo.MboSet;
import psdi.plusp.app.workorder.PlusPWO;
import psdi.plusp.app.workorder.PlusPWORemote;
import psdi.util.MXException;

public class CustomWO extends PlusPWO implements PlusPWORemote{

    public CustomWO(MboSet woSet) throws MXException, RemoteException {
        super(woSet);
    }
    
    @Override
    protected void save() throws MXException, RemoteException {
        // Method called in order to process custom rules
        CustomRules.processRules(this);
        
        super.save();
    }

}

In this example, we worked on the WORKORDER object, so we extended PlusPWO.java. We process our custom rules before saving the object, so we have overridden save method. More information on extending MBO classes can be found in this developerworks link. We create custom MBO and MBOSet classes, put them in maximo.ear/businessobjects.jar/com/custom/app/workorder folder and update the Class field for WORKORDER object from Database Configuration application. It needs application server to be restarted and Database to be configured.

All in all, this solution covers a simple, one-time development to prevent continuous customization and presents configurations for standard Maximo actions and conditions or error messages as if they are used in Workflow designer or Escalations etc.

[{"Business Unit":{"code":"BU059","label":"IBM Software w\/o TPS"},"Product":{"code":"SSLKT6","label":"IBM Maximo Asset Management"},"Component":"","Platform":[{"code":"PF025","label":"Platform Independent"}],"Version":"","Edition":"","Line of Business":{"code":"LOB59","label":"Sustainability Software"}}]

UID

ibm11130427